pax_global_header 0000666 0000000 0000000 00000000064 13610327166 0014517 g ustar 00root root 0000000 0000000 52 comment=6a180252261b94b72959ac8fa562c916c99ecfaf
flowblade-2.4.0.1-fix_release/ 0000775 0000000 0000000 00000000000 13610327166 0016104 5 ustar 00root root 0000000 0000000 flowblade-2.4.0.1-fix_release/.gitignore 0000664 0000000 0000000 00000000562 13610327166 0020077 0 ustar 00root root 0000000 0000000 # Flowblade .gitignore
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
mlt.py
# C extensions
*.so
*.po~
*.py~
*.pot~
# All java related
*.java
*.class
*.jar
# Phantom 2D contents
/flowblade-trunk/Flowblade/phantom2d
/flowblade-trunk/Flowblade/phantom2d/res
/flowblade-trunk/Flowblade/phantom2d/phantom_build
/flowblade-trunk/Flowblade/phantom2d/src
flowblade-2.4.0.1-fix_release/README.md 0000664 0000000 0000000 00000006665 13610327166 0017400 0 ustar 00root root 0000000 0000000

**Contents:**
1. [Introduction](https://github.com/jliljebl/flowblade#introduction)
1. [Features](https://github.com/jliljebl/flowblade#features)
1. [Releases](https://github.com/jliljebl/flowblade#releases)
1. [Installing Flowblade](https://github.com/jliljebl/flowblade#installing-flowblade)
1. [Docs](https://github.com/jliljebl/flowblade#docs)
1. [Screenshot](https://github.com/jliljebl/flowblade#screenshot)
1. [Forum, Webpage](https://github.com/jliljebl/flowblade#forum-webpage)
1. [Contact](https://github.com/jliljebl/flowblade#contact)
# Introduction
Flowblade is a **multitrack non-linear video editor** for Linux released under **GPL 3 license**.
With Flowblade Movie Editor you can compose movies from video clips, audio clips and graphics files. Clips can be cut at the desired frames, filters can be added to clips, and you can create multilayer composite images using compositor objects.
Flowblade offers a configurable workflow - toolset, its order, default tool and certain timeline behaviours are user settable.
# Features
**Editing:**
* 11 editing tools, 9 of which can be selected to the working set
* 4 methods to insert / overwrite / append clips on the timeline
* Drag'n'Drop clips on the timeline
* Clip and compositor parenting with other clips
* Max. 9 combined video and audio tracks available
**Image compositing:**
* 10 compositors. Mix, zoom, move and rotate source video with keyframed animation tools
* 19 blends. Stardand image blend modes like Add, Hardlight and Overlay are available
* 40+ pattern wipes.
**Image and audio filtering:**
* 50+ image filters: color correction, image effects, distorts, alpha manipulation, blur, edge detection, motion effects, freeze frame, etc.
* 30+ audio filters: keyframed volume mixing, echo, reverb, distort, etc.
**Supported editable media types:**
* Most common video and audio formats, depends on installed MLT/FFMPEG codecs
* JPEG, PNG, TGA, TIFF graphics file types
* SVG vector graphics
* Numbered frame sequences
**Output encoding:**
* Most common video and audio formats, depends on installed MLT/FFMPEG codecs
* User can define rendering by setting FFMpeg args individually
# Releases
**Latest release:** Flowblade Movie Editor 2.2 was released on August 2019.
**Next release:** Flowblade Movie Editor 2.4 is coming in December 2019.
# Installing Flowblade
Installing instructions are available [here](./flowblade-trunk/docs/INSTALLING.md).
# Docs
[FAQ](./flowblade-trunk/docs/FAQ.md)
[Known Issues](./flowblade-trunk/docs/KNOWN_ISSUES.md)
[Roadmap](./flowblade-trunk/docs/ROADMAP.md)
[Release notes](./flowblade-trunk/docs/RELEASE_NOTES.md)
[Creating a translation](./flowblade-trunk/docs/CREATING_TRANSLATION.md)
[Dependencies](./flowblade-trunk/docs/DEPENDENCIES.md)
# Screenshot
[Screenshot 2.0 custom theme](./flowblade-trunk/docs/Screenshot-2-0.png)
[Screenshot 1.4 dark theme](./flowblade-trunk/docs/Screenshot-1-4-dark.png)
These are in the repository */docs* folder.
# Forum, Webpage
For questions and discussion on Flowblade we have a [User Forum](https://github.com/jliljebl/flowblade-forum).
[The project webpage is here](http://jliljebl.github.io/flowblade/).
# Contact
Use the **Issues** tab to give bug reports or to make feature requests.
If needed, contact the project lead for additional information: janne.liljeblad@gmail.com
flowblade-2.4.0.1-fix_release/flowblade-trunk/ 0000775 0000000 0000000 00000000000 13610327166 0021204 5 ustar 00root root 0000000 0000000 flowblade-2.4.0.1-fix_release/flowblade-trunk/AUTHORS 0000664 0000000 0000000 00000000104 13610327166 0022247 0 ustar 00root root 0000000 0000000 FLOWBLADE
Application programmed and designed by:
Janne Liljeblad
flowblade-2.4.0.1-fix_release/flowblade-trunk/COPYING 0000664 0000000 0000000 00000101045 13610327166 0022240 0 ustar 00root root 0000000 0000000 GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them 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 prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If 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 convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
18. Exceptions
The OpenShot Video Editor project hereby grants permission for non-GPL
compatible GStreamer, FFMPEG, and MLT plugins to be used and distributed together with
GStreamer, FFMPEG, MLT, and OpenShot Video Editor. This permission is above and beyond
the permissions granted by the GPL license by which OpenShot Video Editor
is covered. If you modify this code, you may extend this exception to
your version of the code, but you are not obligated to do so. If you do
not wish to do so, delete this exception statement from your version.
OpenShot Video Editor does not contain or use any proprietary codecs. We support
free and open-source codecs, such as Ogg Vorbis and Theora. However, since we use
the ffmpeg library, it is possible to use any ffmpeg supported codec, assuming
you have legal permission to do so.
END OF TERMS AND CONDITIONS flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/ 0000775 0000000 0000000 00000000000 13610327166 0023103 5 ustar 00root root 0000000 0000000 flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/__init__.py 0000664 0000000 0000000 00000000036 13610327166 0025213 0 ustar 00root root 0000000 0000000 #
# This file marks module.
#
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/app.py 0000664 0000000 0000000 00000106474 13610327166 0024251 0 ustar 00root root 0000000 0000000 """
Flowblade Movie Editor is a nonlinear video editor.
Copyright 2012 Janne Liljeblad.
This file is part of Flowblade Movie Editor .
Flowblade Movie Editor 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.
Flowblade Movie Editor is distributed in the hope that 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 Flowblade Movie Editor. If not, see .
"""
"""
Application module.
Handles application initialization, shutdown, opening projects, autosave and changing
sequences.
"""
try:
import pgi
pgi.install_as_gi()
except ImportError:
pass
import gi
from gi.repository import GObject
from gi.repository import GLib
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import Gdk
import locale
import mlt
import hashlib
import os
import sys
import time
import appconsts
import audiomonitoring
import audiowaveform
import audiowaveformrenderer
import clipeffectseditor
import clipmenuaction
import compositeeditor
import dialogs
import dialogutils
import dnd
import edit
import editevent
import editorpersistance
import editorstate
import editorwindow
import gmic
import gui
import keyevents
import keyframeeditor
import keyframeeditcanvas
import kftoolmode
import medialog
import mltenv
import mltfilters
import mltplayer
import mltprofiles
import mlttransitions
import modesetting
import movemodes
import multitrimmode
import persistance
import positionbar
import preferenceswindow
import processutils
import projectaction
import projectdata
import projectinfogui
import propertyeditorbuilder
import proxyediting
import render
import renderconsumer
import respaths
import resync
import rotomask
import sequence
import shortcuts
import snapping
import threading
import titler
import tlinewidgets
import toolsintegration
import trimmodes
import translations
import undo
import updater
import userfolders
import utils
import workflow
AUTOSAVE_DIR = appconsts.AUTOSAVE_DIR
AUTOSAVE_FILE = "autosave/autosave"
instance_autosave_id_str = None
PID_FILE = "flowbladepidfile"
BATCH_DIR = "batchrender/"
autosave_timeout_id = -1
recovery_dialog_id = -1
sdl2_timeout_id = -1
loaded_autosave_file = None
splash_screen = None
splash_timeout_id = -1
exit_timeout_id = -1
window_resize_id = -1
window_state_id = -1
resize_timeout_id = -1
_log_file = None
assoc_file_path = None
assoc_timeout_id = None
def main(root_path):
"""
Called at application start.
Initializes application with a default project.
"""
# DEBUG: Direct output to log file if log file set
if _log_file != None:
log_print_output_to_file()
print("Application version: " + editorstate.appversion)
# Print OS, Python version and GTK+ version
try:
os_release_file = open("/etc/os-release","r")
os_text = os_release_file.read()
s_index = os_text.find("PRETTY_NAME=")
e_index = os_text.find("\n", s_index)
print("OS: " + os_text[s_index + 13:e_index - 1])
except:
pass
print("Python", sys.version)
gtk_version = "%s.%s.%s" % (Gtk.get_major_version(), Gtk.get_minor_version(), Gtk.get_micro_version())
print("GTK+ version:", gtk_version)
editorstate.gtk_version = gtk_version
try:
editorstate.mlt_version = mlt.LIBMLT_VERSION
except:
editorstate.mlt_version = "0.0.99" # magic string for "not found"
# Create user folders if needed and determine if we're using xdg or dotfile userf folders.
userfolders.init()
# Set paths.
respaths.set_paths(root_path)
# Load editor prefs and list of recent projects
editorpersistance.load()
if editorpersistance.prefs.theme != appconsts.LIGHT_THEME:
respaths.apply_dark_theme()
if editorpersistance.prefs.display_all_audio_levels == False:
editorstate.display_all_audio_levels = False
editorpersistance.save()
# Init translations module with translations data
translations.init_languages()
translations.load_filters_translations()
mlttransitions.init_module()
# Apr-2017 - SvdB - Keyboard shortcuts
shortcuts.load_shortcut_files()
shortcuts.load_shortcuts()
# Aug-2019 - SvdB - AS
# The test for len != 4 is to make sure that if we change the number of values below the prefs are reset to the correct list
# So when we add or remove a value, make sure we also change the len test
# Only use positive numbers.
if( not editorpersistance.prefs.AUTO_SAVE_OPTS or len(editorpersistance.prefs.AUTO_SAVE_OPTS) != 4):
print("Initializing Auto Save Options")
editorpersistance.prefs.AUTO_SAVE_OPTS = ((0, _("No Autosave")),(1, _("1 min")),(2, _("2 min")),(5, _("5 min")))
# We respaths and translations data available so we need to init in a function.
workflow.init_data()
# RHEL7/CentOS compatibility fix
if gtk_version == "3.8.8":
GObject.threads_init()
# Init gtk threads
Gdk.threads_init()
Gdk.threads_enter()
# Themes
if editorpersistance.prefs.theme == appconsts.FLOWBLADE_THEME:
success = gui.apply_gtk_css()
if not success:
editorpersistance.prefs.theme = appconsts.LIGHT_THEME
editorpersistance.save()
if editorpersistance.prefs.theme != appconsts.LIGHT_THEME:
Gtk.Settings.get_default().set_property("gtk-application-prefer-dark-theme", True)
# Load drag'n'drop images
dnd.init()
# Save screen size data and modify rendering based on screen size/s and number of monitors.
scr_w, scr_h = _set_screen_size_data()
_set_draw_params()
# Refuse to run on too small screen.
if scr_w < 1151 or scr_h < 767:
_too_small_screen_exit()
return
# Splash screen
if editorpersistance.prefs.display_splash_screen == True:
show_splash_screen()
# Init MLT framework
repo = mlt.Factory().init()
processutils.prepare_mlt_repo(repo)
# Set numeric locale to use "." as radix, MLT initilizes this to OS locale and this causes bugs.
locale.setlocale(locale.LC_NUMERIC, 'C')
# Check for codecs and formats on the system.
mltenv.check_available_features(repo)
renderconsumer.load_render_profiles()
# Load filter and compositor descriptions from xml files.
mltfilters.load_filters_xml(mltenv.services)
mlttransitions.load_compositors_xml(mltenv.transitions)
# Replace some services if better replacements available.
mltfilters.replace_services(mltenv.services)
# Create list of available mlt profiles.
mltprofiles.load_profile_list()
# Save assoc file path if found in arguments.
global assoc_file_path
assoc_file_path = get_assoc_file_path()
# There is always a project open, so at startup we create a default project.
# Set default project as the project being edited.
editorstate.project = projectdata.get_default_project()
check_crash = True
# Audiomonitoring being available needs to be known before GUI creation.
audiomonitoring.init(editorstate.project.profile)
# Set trim view mode to current default value.
editorstate.show_trim_view = editorpersistance.prefs.trim_view_default
# Check for tools and init tools integration.
gmic.test_availablity()
toolsintegration.init()
# Create player object.
create_player()
# Create main window and set widget handles in gui.py for more convenient reference.
create_gui()
# Inits widgets with project data.
init_project_gui()
# Inits widgets with current sequence data.
init_sequence_gui()
# Launch player now that data and gui exist
launch_player()
# Editor and modules need some more initializing.
init_editor_state()
# Tracks need to be recentered if window is resized.
# Connect listener for this now that the tline panel size allocation is sure to be available.
global window_resize_id, window_state_id
window_resize_id = gui.editor_window.window.connect("size-allocate", lambda w, e:updater.window_resized())
window_state_id = gui.editor_window.window.connect("window-state-event", lambda w, e:updater.window_resized())
# Get existing autosave files
autosave_files = get_autosave_files()
# Show splash
if ((editorpersistance.prefs.display_splash_screen == True) and len(autosave_files) == 0) and not editorstate.runtime_version_greater_then_test_version(editorpersistance.prefs.workflow_dialog_last_version_shown, editorstate.appversion):
global splash_timeout_id
splash_timeout_id = GLib.timeout_add(2600, destroy_splash_screen)
splash_screen.show_all()
appconsts.SAVEFILE_VERSION = projectdata.SAVEFILE_VERSION # THIS IS A QUESTIONABLE IDEA TO SIMPLIFY IMPORTS, NOT DRY. WHEN DOING TOOLS THAT RUN IN ANOTHER PROCESSES AND SAVE PROJECTS, THIS LINE NEEDS TO BE THERE ALSO.
# Every running instance has unique autosave file which is deleted at exit
set_instance_autosave_id()
# Existance of autosave file hints that program was exited abnormally.
if check_crash == True and len(autosave_files) > 0:
if len(autosave_files) == 1:
GObject.timeout_add(10, autosave_recovery_dialog)
else:
GObject.timeout_add(10, autosaves_many_recovery_dialog)
else:
start_autosave()
# We prefer to monkeypatch some callbacks into some modules, usually to
# maintain a simpler and/or non-circular import structure.
monkeypatch_callbacks()
# File in assoc_file_path is opened after very short delay.
if not(check_crash == True and len(autosave_files) > 0):
if assoc_file_path != None:
print("Launch assoc file:", assoc_file_path)
global assoc_timeout_id
assoc_timeout_id = GObject.timeout_add(10, open_assoc_file)
if editorpersistance.prefs.theme == appconsts.FLOWBLADE_THEME:
gui.apply_flowblade_theme_fixes()
# SDL 2 consumer needs to created after Gtk.main() has run enough for window to be visble
#if editorstate.get_sdl_version() == editorstate.SDL_2: # needs more state considerion still
# print "SDL2 timeout launch"
# global sdl2_timeout_id
# sdl2_timeout_id = GObject.timeout_add(1500, create_sdl_2_consumer)
# In PositionNumericalEntries we are using Gtk.Entry objects in a way that works for us nicely, but is somehow "error" for Gtk, so we just kill this.
Gtk.Settings.get_default().set_property("gtk-error-bell", False)
# Show first run worflow info dialog if not shown for this version of application.
if editorstate.runtime_version_greater_then_test_version(editorpersistance.prefs.workflow_dialog_last_version_shown, editorstate.appversion):
GObject.timeout_add(500, show_worflow_info_dialog)
# Handle userfolders init error and data copy.
if userfolders.get_init_error() != None:
GObject.timeout_add(500, show_user_folders_init_error_dialog, userfolders.get_init_error())
elif userfolders.data_copy_needed():
GObject.timeout_add(500, show_user_folders_copy_dialog)
else:
print("No user folders actions needed.")
# Launch gtk+ main loop
Gtk.main()
Gdk.threads_leave()
# ----------------------------------- callback setting
def monkeypatch_callbacks():
# We need to do this on app start-up or
# we'll get circular imports with projectaction->mltplayer->render->projectaction
render.open_media_file_callback = projectaction.open_rendered_file
# Set callback for undo/redo ops, batcherrender app does not need this
undo.set_post_undo_redo_callback(modesetting.set_post_undo_redo_edit_mode)
undo.repaint_tline = updater.repaint_tline
# # Drag'n'drop callbacks
dnd.add_current_effect = clipeffectseditor.add_currently_selected_effect
dnd.display_monitor_media_file = updater.set_and_display_monitor_media_file
dnd.range_log_items_tline_drop = editevent.tline_range_item_drop
dnd.range_log_items_log_drop = medialog.clips_drop
dnd.open_dropped_files = projectaction.open_file_names
# Media log
medialog.do_multiple_clip_insert_func = editevent.do_multiple_clip_insert
editevent.display_clip_menu_pop_up = clipmenuaction.display_clip_menu
editevent.compositor_menu_item_activated = clipmenuaction._compositor_menu_item_activated
# Posionbar in gmic.py doesnot need trimmodes.py dependency and is avoided
positionbar.trimmodes_set_no_edit_trim_mode = trimmodes.set_no_edit_trim_mode
# Snapping is done in a separate module but needs some tlinewidgets state info
snapping._get_frame_for_x_func = tlinewidgets.get_frame
snapping._get_x_for_frame_func = tlinewidgets._get_frame_x
# Callback to reinit to change slider <-> kf editor
propertyeditorbuilder.re_init_editors_for_slider_type_change_func = clipeffectseditor.effect_selection_changed
propertyeditorbuilder.show_rotomask_func = rotomask.show_rotomask
multitrimmode.set_default_mode_func = modesetting.set_default_edit_mode
keyframeeditor._get_current_edited_compositor = compositeeditor.get_compositor
#keyframeeditor.add_fade_out_func = compositeeditor._add_fade_out_pressed
# These provide clues for further module refactoring
# ---------------------------------- SDL2 consumer
#def create_sdl_2_consumer():
# GObject.source_remove(sdl2_timeout_id)
# print "Creating SDL2 consumer..."
# editorstate.PLAYER().create_sdl2_video_consumer()
# ---------------------------------- program, sequence and project init
def get_assoc_file_path():
"""
Check if were opening app with file association launch from Gnome
"""
arg_str = ""
for arg in sys.argv:
ext_index = arg.find(".flb")
if ext_index != -1:
arg_str = arg
if len(arg_str) == 0:
return None
else:
return arg_str
def open_assoc_file():
GObject.source_remove(assoc_timeout_id)
projectaction.actually_load_project(assoc_file_path, block_recent_files=False)
def create_gui():
"""
Called at app start to create gui objects and handles for them.
"""
tlinewidgets.load_icons()
kftoolmode.load_icons()
updater.set_clip_edit_mode_callback = modesetting.set_clip_monitor_edit_mode
updater.load_icons()
# Notebook indexes are different for 1 and 2 window layouts
if editorpersistance.prefs.global_layout != appconsts.SINGLE_WINDOW:
medialog.range_log_notebook_index = 0
compositeeditor.compositor_notebook_index = 2
clipeffectseditor.filters_notebook_index = 1
# Create window and all child components
editor_window = editorwindow.EditorWindow()
# Make references to various gui components available via gui module
gui.capture_references(editor_window)
# All widgets are now realized and references captured so can find out theme colors
gui.set_theme_colors()
tlinewidgets.set_dark_bg_color()
gui.pos_bar.set_dark_bg_color()
# Connect window global key listener
gui.editor_window.window.connect("key-press-event", keyevents.key_down)
if editorpersistance.prefs.global_layout != appconsts.SINGLE_WINDOW:
gui.editor_window.window2.connect("key-press-event", keyevents.key_down)
# Give undo a reference to uimanager for menuitem state changes
undo.set_menu_items(gui.editor_window.uimanager)
updater.display_sequence_in_monitor()
def create_player():
"""
Creates mlt player object
"""
# Create player and make available from editorstate module.
editorstate.player = mltplayer.Player(editorstate.project.profile)
editorstate.player.set_tracktor_producer(editorstate.current_sequence().tractor)
def launch_player():
# Create SDL output consumer
editorstate.player.set_sdl_xwindow(gui.tline_display)
editorstate.player.create_sdl_consumer()
# Display current sequence tractor
updater.display_sequence_in_monitor()
# Connect buttons to player methods
gui.editor_window.connect_player(editorstate.player)
# Start player.
editorstate.player.connect_and_start()
def init_project_gui():
"""
Called after project load to initialize interface
"""
# Display media files in "Media" tab
gui.media_list_view.fill_data_model()
try: # Fails if current bin is empty
selection = gui.media_list_view.treeview.get_selection()
selection.select_path("0")
except Exception:
pass
# Display bins in "Media" tab
gui.bin_list_view.fill_data_model()
selection = gui.bin_list_view.treeview.get_selection()
selection.select_path("0")
gui.editor_window.bin_info.display_bin_info()
# Display sequences in "Project" tab
gui.sequence_list_view.fill_data_model()
selection = gui.sequence_list_view.treeview.get_selection()
selected_index = editorstate.project.sequences.index(editorstate.current_sequence())
selection.select_path(str(selected_index))
# Display logged ranges in "Range Log" tab
medialog.update_group_select_for_load()
medialog.update_media_log_view()
render.set_default_values_for_widgets(True)
gui.tline_left_corner.update_gui()
projectinfogui.update_project_info()
titler.reset_titler()
# Set render folder selector to last render if prefs require
folder_path = editorstate.PROJECT().get_last_render_folder()
if folder_path != None and editorpersistance.prefs.remember_last_render_dir == True:
gui.render_out_folder.set_current_folder(folder_path)
def init_sequence_gui():
"""
Called after project load or changing current sequence
to initialize interface.
"""
# Set correct compositing mode menu item selected
gui.editor_window.init_compositing_mode_menu()
# Set initial timeline scale draw params
editorstate.current_sequence().update_length()
updater.update_pix_per_frame_full_view()
updater.init_tline_scale()
updater.repaint_tline()
def init_editor_state():
"""
Called after project load or changing current sequence
to initalize editor state.
"""
render.fill_out_profile_widgets()
gui.media_view_filter_selector.set_pixbuf(editorstate.media_view_filter)
gui.editor_window.window.set_title(editorstate.project.name + " - Flowblade")
gui.editor_window.uimanager.get_widget("/MenuBar/FileMenu/Save").set_sensitive(False)
gui.editor_window.uimanager.get_widget("/MenuBar/EditMenu/Undo").set_sensitive(False)
gui.editor_window.uimanager.get_widget("/MenuBar/EditMenu/Redo").set_sensitive(False)
# Center tracks vertical display and init some listeners to
# new value and repaint tracks column.
tlinewidgets.set_ref_line_y(gui.tline_canvas.widget.get_allocation())
gui.tline_column.init_listeners()
gui.tline_column.widget.queue_draw()
# Clear editors
clipeffectseditor.clear_clip()
clipeffectseditor.effect_selection_changed() # to get No Clip text
compositeeditor.clear_compositor()
# Show first pages on notebooks
gui.middle_notebook.set_current_page(0)
# Clear clip selection.
movemodes.clear_selection_values()
# Set initial edit mode
modesetting.set_default_edit_mode()
# Create array needed to update compositors after all edits
editorstate.current_sequence().restack_compositors()
proxyediting.set_menu_to_proxy_state()
undo.clear_undos()
# Enable edit action GUI updates
edit.do_gui_update = True
def new_project(profile_index, v_tracks, a_tracks):
sequence.VIDEO_TRACKS_COUNT = v_tracks
sequence.AUDIO_TRACKS_COUNT = a_tracks
profile = mltprofiles.get_profile_for_index(profile_index)
new_project = projectdata.Project(profile)
open_project(new_project)
def open_project(new_project):
stop_autosave()
gui.editor_window.window.handler_block(window_resize_id)
gui.editor_window.window.handler_block(window_state_id)
audiomonitoring.close_audio_monitor()
audiowaveformrenderer.clear_cache()
audiowaveform.frames_cache = {}
editorstate.project = new_project
editorstate.media_view_filter = appconsts.SHOW_ALL_FILES
# Inits widgets with project data
init_project_gui()
# Inits widgets with current sequence data
init_sequence_gui()
# Set and display current sequence tractor
display_current_sequence()
# Editor and modules need some more initializing
init_editor_state()
# For save time message on close
projectaction.save_time = None
# Delete autosave file after it has been loaded
global loaded_autosave_file
if loaded_autosave_file != None:
print("Deleting", loaded_autosave_file)
os.remove(loaded_autosave_file)
loaded_autosave_file = None
editorstate.update_current_proxy_paths()
editorstate.fade_length = -1
editorstate.transition_length = -1
editorstate.clear_trim_clip_cache()
audiomonitoring.init_for_project_load()
start_autosave()
if new_project.update_media_lengths_on_load == True:
projectaction.update_media_lengths()
gui.editor_window.set_default_edit_tool()
editorstate.trim_mode_ripple = False
updater.set_timeline_height()
gui.editor_window.window.handler_unblock(window_resize_id)
gui.editor_window.window.handler_unblock(window_state_id)
global resize_timeout_id
resize_timeout_id = GLib.timeout_add(500, _do_window_resized_update)
# Set scrubbing
editorstate.player.set_scrubbing(editorpersistance.prefs.audio_scrubbing)
def _do_window_resized_update():
GObject.source_remove(resize_timeout_id)
updater.window_resized()
def change_current_sequence(index):
stop_autosave()
editorstate.project.c_seq = editorstate.project.sequences[index]
# Inits widgets with current sequence data
init_sequence_gui()
# update resync data
resync.sequence_changed(editorstate.project.c_seq)
# Set and display current sequence tractor
display_current_sequence()
# Editor and modules needs to do some initializing
init_editor_state()
# Display current sequence selected in gui.
gui.sequence_list_view.fill_data_model()
selection = gui.sequence_list_view.treeview.get_selection()
selected_index = editorstate.project.sequences.index(editorstate.current_sequence())
selection.select_path(str(selected_index))
audiomonitoring.recreate_master_meter_filter_for_new_sequence()
start_autosave()
updater.set_timeline_height()
def display_current_sequence():
# Get shorter alias.
player = editorstate.player
player.consumer.stop()
player.init_for_profile(editorstate.project.profile)
player.create_sdl_consumer()
player.set_tracktor_producer(editorstate.current_sequence().tractor)
player.connect_and_start()
updater.display_sequence_in_monitor()
player.seek_frame(0)
updater.repaint_tline()
# ------------------------------------------------- autosave
def autosave_recovery_dialog():
dialogs.autosave_recovery_dialog(autosave_dialog_callback, gui.editor_window.window)
return False
def autosave_dialog_callback(dialog, response):
dialog.destroy()
autosave_file = userfolders.get_cache_dir() + AUTOSAVE_DIR + get_autosave_files()[0]
if response == Gtk.ResponseType.OK:
global loaded_autosave_file
loaded_autosave_file = autosave_file
projectaction.actually_load_project(autosave_file, True)
else:
os.remove(autosave_file)
start_autosave()
def autosaves_many_recovery_dialog():
autosaves_file_names = get_autosave_files()
now = time.time()
autosaves = []
for a_file_name in autosaves_file_names:
autosave_path = userfolders.get_cache_dir() + AUTOSAVE_DIR + a_file_name
autosave_object = utils.EmptyClass()
autosave_object.age = now - os.stat(autosave_path).st_mtime
autosave_object.path = autosave_path
autosaves.append(autosave_object)
autosaves = sorted(autosaves, key=lambda autosave_object: autosave_object.age)
dialogs.autosaves_many_recovery_dialog(autosaves_many_dialog_callback, autosaves, gui.editor_window.window)
return False
def autosaves_many_dialog_callback(dialog, response, autosaves_view, autosaves):
if response == Gtk.ResponseType.OK:
autosave_file = autosaves[autosaves_view.get_selected_indexes_list()[0]].path # Single selection, 1 quaranteed to exist
print("autosave_file", autosave_file)
global loaded_autosave_file
loaded_autosave_file = autosave_file
dialog.destroy()
projectaction.actually_load_project(autosave_file, True)
else:
dialog.destroy()
start_autosave()
def set_instance_autosave_id():
global instance_autosave_id_str
instance_autosave_id_str = "_" + hashlib.md5(str(os.urandom(32)).encode('utf-8')).hexdigest()
def get_instance_autosave_file():
return AUTOSAVE_FILE + instance_autosave_id_str
def start_autosave():
global autosave_timeout_id
# Aug-2019 - SvdB - AS - Made changes to use the value stored in prefs, with Default=1 minute, rather than hardcoding
try:
time_min, desc = editorpersistance.prefs.AUTO_SAVE_OPTS[editorpersistance.prefs.auto_save_delay_value_index]
except:
time_min = 1
autosave_delay_millis = time_min * 60 * 1000
# Aug-2019 - SvdB - AS - put in code to stop or not start autosave depending on user selection
if autosave_delay_millis > 0:
print("Autosave started...")
autosave_timeout_id = GObject.timeout_add(autosave_delay_millis, do_autosave)
autosave_file = userfolders.get_cache_dir() + get_instance_autosave_file()
persistance.save_project(editorstate.PROJECT(), autosave_file)
else:
print("Autosave disabled...")
stop_autosave()
def get_autosave_files():
autosave_dir = userfolders.get_cache_dir() + AUTOSAVE_DIR
return os.listdir(autosave_dir)
def stop_autosave():
global autosave_timeout_id
if autosave_timeout_id == -1:
return
GObject.source_remove(autosave_timeout_id)
autosave_timeout_id = -1
def do_autosave():
autosave_file = userfolders.get_cache_dir() + get_instance_autosave_file()
persistance.save_project(editorstate.PROJECT(), autosave_file)
return True
# ------------------------------------------------- splash screen
def show_splash_screen():
global splash_screen
splash_screen = Gtk.Window(Gtk.WindowType.TOPLEVEL)
splash_screen.set_border_width(0)
splash_screen.set_decorated(False)
splash_screen.set_position(Gtk.WindowPosition.CENTER)
img = Gtk.Image.new_from_file(respaths.IMAGE_PATH + "flowblade_splash_black_small.png")
splash_screen.add(img)
splash_screen.set_keep_above(True)
splash_screen.set_size_request(498, 320) # Splash screen is working funny since Ubuntu 13.10
splash_screen.set_resizable(False)
while(Gtk.events_pending()):
Gtk.main_iteration()
def destroy_splash_screen():
splash_screen.destroy()
GObject.source_remove(splash_timeout_id)
def show_worflow_info_dialog():
editorpersistance.prefs.workflow_dialog_last_version_shown = editorstate.appversion
editorpersistance.save()
worflow_info_dialog = workflow.WorkflowDialog()
return False
# ------------------------------------------------------- userfolders dialogs
def show_user_folders_init_error_dialog(error_msg):
# not done
print(error_msg, " user folder XDG init error")
return False
def show_user_folders_copy_dialog():
dialog = dialogs.xdg_copy_dialog()
copy_thread = userfolders.XDGCopyThread(dialog, _xdg_copy_completed_callback)
copy_thread.start()
return False
def _xdg_copy_completed_callback(dialog):
Gdk.threads_enter()
dialog.destroy()
Gdk.threads_leave()
# ------------------------------------------------------- small and multiple screens
# We need a bit more stuff because having multiple monitors can mix up setting draw parameters.
def _set_screen_size_data():
monitor_data = utils.get_display_monitors_size_data()
monitor_data_index = editorpersistance.prefs.layout_display_index
display = Gdk.Display.get_default()
num_monitors = display.get_n_monitors() # Get number of monitors.
if monitor_data_index == 0:
scr_w = Gdk.Screen.width()
scr_h = Gdk.Screen.height()
print("Using Full Screen size for layout:", scr_w, "x", scr_h)
elif monitor_data_index > num_monitors:
print("Specified layout monitor not present.")
scr_w = Gdk.Screen.width()
scr_h = Gdk.Screen.height()
print("Using Full Screen size for layout:", scr_w, "x", scr_h)
editorpersistance.prefs.layout_display_index = 0
else:
scr_w, scr_h = monitor_data[monitor_data_index]
if scr_w < 1151 or scr_h < 767:
print("Selected layout monitor too small.")
scr_w = Gdk.Screen.width()
scr_h = Gdk.Screen.height()
print("Using Full Screen size for layout:", scr_w, "x", scr_h)
editorpersistance.prefs.layout_display_index = 0
else:
# Selected monitor data is available and monitor is usable as layout monitor.
print("Using monitor " + str(monitor_data_index) + " for layout: " + str(scr_w) + " x " + str(scr_h))
editorstate.SCREEN_WIDTH = scr_w
editorstate.SCREEN_HEIGHT = scr_h
print("Small height:", editorstate.screen_size_small_height())
print("Small width:", editorstate.screen_size_small_width())
return (scr_w, scr_h)
# Adjust gui parameters for smaller screens
def _set_draw_params():
if editorstate.screen_size_small_width() == True:
appconsts.NOTEBOOK_WIDTH = 400
editorwindow.MONITOR_AREA_WIDTH = 400
editorwindow.MEDIA_MANAGER_WIDTH = 100
if editorstate.screen_size_small_height() == True:
appconsts.TOP_ROW_HEIGHT = 10
projectinfogui.PROJECT_INFO_PANEL_HEIGHT = 140
tlinewidgets.HEIGHT = 252
if editorstate.screen_size_large_height() == True:
keyframeeditcanvas.GEOMETRY_EDITOR_HEIGHT = 300
if editorstate.SCREEN_WIDTH < 1153 or editorstate.SCREEN_HEIGHT < 865:
editorwindow.MONITOR_AREA_WIDTH = 400
positionbar.BAR_WIDTH = 100
if editorpersistance.prefs.double_track_hights == True:
appconsts.TRACK_HEIGHT_NORMAL = 100 # track height in canvas and column
appconsts.TRACK_HEIGHT_SMALL = 50 # track height in canvas and column
appconsts.TRACK_HEIGHT_SMALLEST = 50 # maybe remove as it is no longer meaningful
appconsts.TLINE_HEIGHT = 520
sequence.TRACK_HEIGHT_NORMAL = appconsts.TRACK_HEIGHT_NORMAL # track height in canvas and column
sequence.TRACK_HEIGHT_SMALL = appconsts.TRACK_HEIGHT_SMALL # track height in canvas and column
tlinewidgets.set_tracks_double_height_consts()
def _too_small_screen_exit():
global exit_timeout_id
exit_timeout_id = GObject.timeout_add(200, _show_too_small_info)
# Launch gtk+ main loop
Gtk.main()
def _show_too_small_info():
GObject.source_remove(exit_timeout_id)
primary_txt = _("Too small screen for this application.")
scr_w = Gdk.Screen.width()
scr_h = Gdk.Screen.height()
secondary_txt = _("Minimum screen dimensions for this application are 1152 x 768.\n") + \
_("Your screen dimensions are ") + str(scr_w) + " x " + str(scr_h) + "."
dialogutils.warning_message_with_callback(primary_txt, secondary_txt, None, False, _early_exit)
def _early_exit(dialog, response):
dialog.destroy()
# Exit gtk main loop.
Gtk.main_quit()
# ------------------------------------------------------- logging
def log_print_output_to_file():
so = se = open(_log_file, 'w', 0)
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())
# ------------------------------------------------------ shutdown
def shutdown():
dialogs.exit_confirm_dialog(_shutdown_dialog_callback, get_save_time_msg(), gui.editor_window.window, editorstate.PROJECT().name)
return True # Signal that event is handled, otherwise it'll destroy window anyway
def get_save_time_msg():
return projectaction.get_save_time_msg()
def _shutdown_dialog_callback(dialog, response_id):
dialog.destroy()
if response_id == Gtk.ResponseType.CLOSE:# "Don't Save"
pass
elif response_id == Gtk.ResponseType.YES:# "Save"
if editorstate.PROJECT().last_save_path != None:
persistance.save_project(editorstate.PROJECT(), editorstate.PROJECT().last_save_path)
else:
dialogutils.warning_message(_("Project has not been saved previously"),
_("Save project with File -> Save As before closing."),
gui.editor_window.window)
return
else: # "Cancel"
return
# --- APP SHUT DOWN --- #
print("Exiting app...")
# Sep-2018 - SvdB - Stop wave form threads
for thread_termination in threading.enumerate():
# We only terminate threads with a 'process', as these are launched
# by the audiowaveformrenderer
try:
thread_termination.process.terminate()
except:
None
# No more auto saving
stop_autosave()
# Save window dimensions on exit
alloc = gui.editor_window.window.get_allocation()
x, y, w, h = alloc.x, alloc.y, alloc.width, alloc.height
editorpersistance.prefs.exit_allocation = (w, h)
if gui.editor_window.window2 != None:
alloc = gui.editor_window.window2.get_allocation()
pos_x, pos_y = gui.editor_window.window2.get_position()
editorpersistance.prefs.exit_allocation_window_2 = (alloc.width, alloc.height, pos_x, pos_y)
editorpersistance.prefs.app_v_paned_position = gui.editor_window.app_v_paned.get_position()
editorpersistance.prefs.top_paned_position = gui.editor_window.top_paned.get_position()
try: # This fails if preference for top row layout changed, we just ignore saving these values then.
if editorwindow.top_level_project_panel() == True:
editorpersistance.prefs.mm_paned_position = 200 # This is not used until user sets preference to not have top level project panel
else:
editorpersistance.prefs.mm_paned_position = gui.editor_window.mm_paned.get_position()
except:
pass
editorpersistance.save()
# Block reconnecting consumer before setting window not visible
updater.player_refresh_enabled = False
gui.editor_window.window.set_visible(False)
if gui.editor_window.window2 != None:
gui.editor_window.window2.set_visible(False)
# Close and destroy app when gtk finds time to do it after hiding window
GLib.idle_add(_app_destroy)
def _app_destroy():
# Close threads and stop mlt consumers
editorstate.player.shutdown() # has ticker thread and player threads running
audiomonitoring.close()
# Wait threads to stop
while((editorstate.player.ticker.exited == False) and
(audiomonitoring._update_ticker.exited == False) and
(audiowaveform.waveform_thread != None)):
pass
# Delete autosave file
try:
os.remove(userfolders.get_cache_dir() + get_instance_autosave_file())
except:
print("Delete autosave file FAILED")
# Exit gtk main loop.
Gtk.main_quit()
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/appconsts.py 0000664 0000000 0000000 00000016134 13610327166 0025474 0 ustar 00root root 0000000 0000000 """
Flowblade Movie Editor is a nonlinear video editor.
Copyright 2012 Janne Liljeblad.
This file is part of Flowblade Movie Editor .
Flowblade Movie Editor 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.
Flowblade Movie Editor is distributed in the hope that 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 Flowblade Movie Editor. If not, see .
"""
"""
Module contains constant values that are used by multiple modules in the application.
"""
PROJECT_FILE_EXTENSION = ".flb"
# Media types for tracks or clips
UNKNOWN = 0
VIDEO = 1
AUDIO = 2
IMAGE = 3
RENDERED_VIDEO = 4 # not used
PATTERN_PRODUCER = 5
SYNC_AUDIO = 6
FILE_DOES_NOT_EXIST = 7
IMAGE_SEQUENCE = 8
# Media view filtering options
SHOW_ALL_FILES = 0
SHOW_VIDEO_FILES = 1
SHOW_AUDIO_FILES = 2
SHOW_GRAPHICS_FILES = 3
SHOW_IMAGE_SEQUENCES = 4
SHOW_PATTERN_PRODUCERS = 5
# These are used to draw indicators that tell if more frames are available while trimming
ON_FIRST_FRAME = 0
ON_LAST_FRAME = 1
ON_BETWEEN_FRAME = 2
# Sync states of sync child clips
SYNC_CORRECT = 0
SYNC_OFF = 1
SYNC_PARENT_GONE = 2
# Allowed editing operations on a track
FREE = 0 # All edits allowed
SYNC_LOCKED = 1 # No insert, splice out or one roll trim.
# Allowed edits do not change positions of later clips
LOCKED = 2 # No edits allowed
# Property types of mlt filters and mlt transitions in filters.xml
# and compositors.xml
PROP_INT = 0
PROP_FLOAT = 1
PROP_EXPRESSION = 2
# Display heights for tracks and timeline.
TRACK_HEIGHT_NORMAL = 50 # track height in canvas and column
TRACK_HEIGHT_SMALL = 25 # track height in canvas and column
TRACK_HEIGHT_SMALLEST = 25 # maybe remove this as it is no longer used
TLINE_HEIGHT = 260
# Notebook widths
NOTEBOOK_WIDTH = 600 # defines app min width together with MONITOR_AREA_WIDTH
NOTEBOOK_WIDTH_WIDESCREEN = 500
TOP_ROW_HEIGHT = 500
# Property editing gui consts
PROPERTY_ROW_HEIGHT = 22
PROPERTY_NAME_WIDTH = 90
# Clip mute options
MUTE_NOTHING = 0
MUTE_AUDIO = 1
MUTE_VIDEO = 2
MUTE_ALL = 3
# Track mute options
TRACK_MUTE_NOTHING = 0
TRACK_MUTE_VIDEO = 1
TRACK_MUTE_AUDIO = 2
TRACK_MUTE_ALL = 3
# XML Attribute and element names used in multiple modules
NAME = "name"
ARGS = "args"
PROPERTY = "property"
NON_MLT_PROPERTY = "propertynonmlt"
MLT_SERVICE = "mlt_service"
EXTRA_EDITOR = "extraeditor"
# Available tracks maximum for Flowblade
MAX_TRACKS = 9
INIT_V_TRACKS = 5
INIT_A_TRACKS = 4
# Thumbnail image dimensions
THUMB_WIDTH = 116
THUMB_HEIGHT = 87
# Magic value for no pan being applied for audio producer
NO_PAN = -99
# Copy of projectdata.SAVEFILE_VERSION is here to be available at savetime without importing projectdata
# This is set at application startup in app.main()
SAVEFILE_VERSION = -1
# This color is used in two modules
MIDBAR_COLOR = "#bdbdbd"
# Media log event types
MEDIA_LOG_ALL = -1 # no MediaLogEvent has this type, this is used when filtering events for display
MEDIA_LOG_INSERT = 0
MEDIA_LOG_MARKS_SET = 1
# Media log item sorting
TIME_SORT = 0
NAME_SORT = 1
COMMENT_SORT = 2
# Rendered clip types
RENDERED_DISSOLVE = 0
RENDERED_WIPE = 1
RENDERED_COLOR_DIP = 2
RENDERED_FADE_IN = 3
RENDERED_FADE_OUT = 4
# Project proxy modes
USE_ORIGINAL_MEDIA = 0
USE_PROXY_MEDIA = 1
CONVERTING_TO_USE_PROXY_MEDIA = 2
CONVERTING_TO_USE_ORIGINAL_MEDIA = 3
# Autosave directory relative path
AUTOSAVE_DIR = "autosave/"
# Hidden media folders
# NOTE: We have not been fully consistant with the ending forward slashes.
AUDIO_LEVELS_DIR = "audiolevels/"
THUMBNAILS_DIR = "thumbnails"
RENDERED_CLIPS_DIR = "rendered_clips"
GMIC_DIR = "gmic"
PHANTOM_DIR = "phantom2d"
PHANTOM_DISK_CACHE_DIR = "disk_cache"
MATCH_FRAME_DIR = "match_frame"
MATCH_FRAME = MATCH_FRAME_DIR + "/match_frame.png"
MATCH_FRAME_NEW = MATCH_FRAME_DIR + "/match_frame_new.png"
TRIM_VIEW_DIR = "trim_view"
USER_PROFILES_DIR = "user_profiles/"
USER_PROFILES_DIR_NO_SLASH = "user_profiles"
BATCH_DIR = "batchrender/"
# Luma bands
SHADOWS = 0
MIDTONES = 1
HIGHLIGHTS = 2
# Multi move edit ops
MULTI_NOOP = 0
MULTI_ADD_TRIM = 1
MULTI_TRIM_REMOVE = 2
MULTI_TRIM = 3
# Jack options (not used currently)
JACK_ON_START_UP_NO = 0
JACK_ON_START_UP_YES = 1
JACK_OUT_AUDIO = 0
JACK_OUT_SYNC = 0
# Media load order options
LOAD_ABSOLUTE_FIRST = 0
LOAD_RELATIVE_FIRST = 1
LOAD_ABSOLUTE_ONLY = 2
# Trim view modes
TRIM_VIEW_ON = 0
TRIM_VIEW_SINGLE = 1
TRIM_VIEW_OFF = 2
# Midbar layout
MIDBAR_TC_LEFT = 0
MIDBAR_TC_CENTER = 1
MIDBAR_COMPONENTS_CENTERED = 2
# Windows mode
SINGLE_WINDOW = 1
TWO_WINDOWS = 2
# Apr-2017 - SvdB
SHORTCUTS_DEFAULT = 'Flowblade Default'
SHORTCUTS_DEFAULT_XML = 'flowblade'
SHORTCUTS_ROOT_TAG = 'flowblade'
SHORTCUTS_TAG = 'shortcuts'
# Project properties keys
P_PROP_DISSOLVE_GROUP_FADE_IN = "P_PROP_DISSOLVE_GROUP_FADE_IN"
P_PROP_DISSOLVE_GROUP_FADE_OUT = "P_PROP_DISSOLVE_GROUP_FADE_OUT"
P_PROP_ANIM_GROUP_FADE_IN = "P_PROP_ANIM_GROUP_FADE_IN"
P_PROP_ANIM_GROUP_FADE_OUT = "P_PROP_ANIM_GROUP_FADE_OUT"
P_PROP_TLINE_SHRINK_VERTICAL = "tline_shrink_vertical"
P_PROP_LAST_RENDER_SELECTIONS = "P_PROP_LAST_RENDER_SELECTIONS"
P_PROP_TRANSITION_ENCODING = "P_PROP_TRANSITION_ENCODING"
P_PROP_AUTO_FOLLOW = "P_PROP_AUTO_FOLLOW"
#P_PROP_COMPOSITING_MODE = "P_PROP_COMPOSITING_MODE"
# A context defining action taken when mouse press happens based on edit mode ands mouse position
POINTER_CONTEXT_NONE = 0
POINTER_CONTEXT_END_DRAG_LEFT = 1
POINTER_CONTEXT_END_DRAG_RIGHT = 2
POINTER_CONTEXT_COMPOSITOR_MOVE = 3
POINTER_CONTEXT_COMPOSITOR_END_DRAG_LEFT = 4
POINTER_CONTEXT_COMPOSITOR_END_DRAG_RIGHT = 5
POINTER_CONTEXT_TRIM_LEFT = 6
POINTER_CONTEXT_TRIM_RIGHT = 7
POINTER_CONTEXT_BOX_SIDEWAYS = 8
POINTER_CONTEXT_MULTI_ROLL = 9
POINTER_CONTEXT_MULTI_SLIP = 10
# Timeline tool ids. NOTE: a tool can map to 1 or more editmodes and even module specified submodes, depending on complexity of edit actions.
TLINE_TOOL_INSERT = 1
TLINE_TOOL_OVERWRITE = 2
TLINE_TOOL_TRIM = 3
TLINE_TOOL_ROLL = 4
TLINE_TOOL_SLIP = 5
TLINE_TOOL_SPACER = 6
TLINE_TOOL_BOX = 7
TLINE_TOOL_RIPPLE_TRIM = 8
TLINE_TOOL_CUT = 9
TLINE_TOOL_KFTOOL = 10
TLINE_TOOL_MULTI_TRIM = 11
# Monitor switch events
MONITOR_TLINE_BUTTON_PRESSED = 1
MONITOR_CLIP_BUTTON_PRESSED = 2
# Appliation thmes and colors preference
FLOWBLADE_THEME = 0
DARK_THEME = 1
LIGHT_THEME = 2
# DND actions
DND_ALWAYS_OVERWRITE = 0
DND_OVERWRITE_NON_V1 = 1
DND_ALWAYS_INSERT = 2
# Top row layouts
THREE_PANELS_IF_POSSIBLE = 0
ALWAYS_TWO_PANELS = 1
# Copypaste data tyoe
COPY_PASTE_DATA_CLIPS = 1
COPY_PASTE_DATA_COMPOSITOR_PROPERTIES = 2
COPY_PASTE_KEYFRAME_EDITOR_KF_DATA = 3
COPY_PASTE_GEOMETRY_EDITOR_KF_DATA = 4
# Timeline Compositong modes
COMPOSITING_MODE_TOP_DOWN_FREE_MOVE = 0
COMPOSITING_MODE_TOP_DOWN_AUTO_FOLLOW = 1
COMPOSITING_MODE_STANDARD_AUTO_FOLLOW = 2
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/atomicfile.py 0000775 0000000 0000000 00000020504 13610327166 0025575 0 ustar 00root root 0000000 0000000 """
Flowblade Movie Editor is a nonlinear video editor.
Copyright 2017 Janne Liljeblad and contributors.
This file is part of Flowblade Movie Editor .
Flowblade Movie Editor 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.
Flowblade Movie Editor is distributed in the hope that 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 Flowblade Movie Editor. If not, see .
"""
"""
Atomic file write support.
Can also be run as a stand-alone script to do ad-hoc testing of the
AtomicFileWriter class.
"""
import hashlib
import os
import sys
MAX_CREATE_FILE_ATTEMPTS = 10
class AtomicFileWriteError(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)
class AtomicFileWriter(object):
"""
Context manager for writing files atomically.
Usage:
with AtomicFileWriter("/path/to/file", "w") as afw:
f = afw.get_file()
# do stuff with writable file object
f.write("hello world\n")
Behind the scenes, a temp file will be created in /path/to/, and when
the context manager block goes out of scope, the temp file will be
atomically renamed to /path/to/file.
POSIX guarantees that rename() is an atomic operation, so on any
reasonable UNIX filesystem, the file will either show up in the
destination path with all of its contents, or it won't show up at all.
Additionally, if a file was already in the destination path, it will
not be corrupted. Either it will be cleanly overwritten, or it will
be preserved in its former state without modification.
"""
def __init__(self, file_path, mode, debug=False):
"""
AtomicFileWriter constructor.
Accepts a file path to write to (eventually), and a file write
mode (either "w" or "wb" for text or binary, respectively).
Also accepts an optional debug boolean argument, which will
print out messages for testing and troubleshooting.
"""
# validate file path
if file_path is None:
raise ValueError("file_path can not be None")
# validate mode
if mode in ("w", "wb"):
self.mode = mode
else:
raise ValueError("AtomicFileWriter only accepts 'w' or 'wb' as valid modes")
# absolute path to the temp file used for writing
self.tmp_file_path = None
# absolute path to the file that the caller eventually wants to write
self.dest_file_path = os.path.abspath(file_path)
# absolute path to the directory containing the files
self.dir_path = os.path.dirname(self.dest_file_path)
# destination base filename (without the parent path)
self.basename = os.path.basename(self.dest_file_path)
# temp file object
self.file_obj = None
# debugging mode
self.debug = debug
def __enter__(self):
"""
Context manager starting point.
Creates a temp file, and returns a reference to this instance.
"""
# try several times to create a new temp file
for i in range(MAX_CREATE_FILE_ATTEMPTS):
# pick a temp filename that we hope is unique
maybe_tmp_filename = self.__get_random_filename(self.basename)
# add the candidate temp filename to the directory where the destination
# file will be written. since the temp and final destination file are
# in the same directory, rename() will never cross filesystems.
maybe_tmp_file_path = os.path.join(self.dir_path, maybe_tmp_filename)
try:
# create the temp file, with a guarantee that it didn't exist before
# this returns a numeric file descriptor
# the mode of 666 is passed in because it will be filtered by the user's umask
fd = os.open(maybe_tmp_file_path, os.O_WRONLY|os.O_CREAT|os.O_EXCL, 0o666)
# if we didn't get an OSError by now, turn the numeric
# file descriptor into a Python file object
self.file_obj = os.fdopen(fd, self.mode)
# remember the temp file path
self.tmp_file_path = maybe_tmp_file_path
if self.debug:
print("Created temp file: '%s'" % (self.tmp_file_path))
# we created a file, stop trying
break
except OSError:
pass
# if we were unable to create a temp file, raise an error
#
# the destination file path is reported in this error instead of the temp file path,
# and although this is slightly innacurate from this low-level context, it will be
# a less surprising error message for a user of the program who really intends to
# write the destination file eventually, and not some temp file
if not self.tmp_file_path:
raise AtomicFileWriteError("Could not open '%s' for writing" % (self.dest_file_path))
# return a reference to this instance so it can be used as a context manager
return self
def __exit__(self, exception_type, exception_value, traceback):
"""
Context manager cleanup.
Flushes and closes the temp file (if necessary),
and atomically renames it into place.
"""
# if we caught an exception from inside the context manager block,
# then remove the temp file and let the exception bubble up
if exception_type:
try:
# close the temp file if necessary
if not self.file_obj.closed:
self.file_obj.close()
# remove the temp file
os.unlink(self.tmp_file_path)
except:
print("Error cleaning up temp file: " + self.tmp_file_path)
# let the original exception that was passed into this method bubble up
return False
# if the caller didn't already close the file
if not self.file_obj.closed:
# flush the file buffer to disk
self.file_obj.flush()
# close the file
self.file_obj.close()
try:
# rename the temp file into the final destination
os.rename(self.tmp_file_path, self.dest_file_path)
if self.debug:
print("Renamed temp file: '%s' to '%s'" % (self.tmp_file_path, self.dest_file_path))
except:
print("Error renaming temp file '%s' to '%s'" % (self.tmp_file_path, self.dest_file_path))
# if the rename didn't work, try to clean up the temp file
try:
os.unlink(self.tmp_file_path)
if self.debug:
print("Removed temp file: '%s'" % (self.tmp_file_path))
except:
print("Error removing temp file '%s' after rename to '%s' failed" % \
(self.tmp_file_path, self.dest_file_path))
# let the os.rename() failure exception bubble up
raise
def get_file(self):
"""
Get a reference to the writable temp file object.
This returns a regular Python file object.
"""
return self.file_obj
def __get_random_filename(self, basepath):
"""
Create a candidate temp filename, without touching the filesystem.
"""
uuid_str = hashlib.md5(str(os.urandom(32)).encode('utf-8')).hexdigest()
return ".tmp-" + uuid_str + "-" + basepath
# command-line mode for testing
def main():
if 2 != len(sys.argv):
sys.stderr.write("usage: python3 atomicfile.py [file_path_to_write]\n")
sys.exit(1)
dest_file = sys.argv[1]
with AtomicFileWriter(dest_file, "w", True) as afw:
f = afw.get_file()
f.write("test file written by atomicfile.AtomicFileWriter\n")
sys.exit(0)
if __name__ == "__main__":
main()
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/audiomonitoring.py 0000664 0000000 0000000 00000056501 13610327166 0026673 0 ustar 00root root 0000000 0000000 """
Flowblade Movie Editor is a nonlinear video editor.
Copyright 2012 Janne Liljeblad.
This file is part of Flowblade Movie Editor .
Flowblade Movie Editor 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.
Flowblade Movie Editor is distributed in the hope that 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 Flowblade Movie Editor. If not, see .
"""
"""
Module handles initializing and displaying audiomonitor tool.
"""
try:
import pgi
pgi.install_as_gi()
except ImportError:
pass
import gi
import cairo
import mlt
import time
from gi.repository import Gtk, GObject
from gi.repository import GLib
from gi.repository import Gdk
from gi.repository import Pango
gi.require_version('PangoCairo', '1.0')
from gi.repository import PangoCairo
import appconsts
import cairoarea
import editorpersistance
import editorstate
import mltrefhold
import guiutils
import utils
SLOT_W = 60
METER_SLOT_H = 458
CONTROL_SLOT_H = 300
Y_TOP_PAD = 12
# Dash pattern used to create "LED"s
DASH_INK = 2.0
DASH_SKIP = 1.0
DASHES = [DASH_INK, DASH_SKIP, DASH_INK, DASH_SKIP]
METER_LIGHTS = 143 #57
METER_HEIGHT = METER_LIGHTS * DASH_INK + (METER_LIGHTS - 1) * DASH_SKIP
METER_WIDTH = 10
# These are calculated using IEC_Scale function in MLT and correspond to level values received here
DB_IEC_MINUS_2 = 0.95
DB_IEC_MINUS_4 = 0.9
DB_IEC_MINUS_6 = 0.85
DB_IEC_MINUS_10 = 0.75
DB_IEC_MINUS_12 = 0.70
DB_IEC_MINUS_20 = 0.5
DB_IEC_MINUS_40 = 0.15
PEAK_FRAMES = 14
OVER_FRAMES = 30
# Colors
METER_BG_COLOR = (0.15, 0.15, 0.15)
OVERLAY_COLOR = (0.70, 0.70, 0.70) #utils.get_cairo_color_tuple_255_rgb(63, 145, 188)#59, 140, 174) #(0.7, 0.7, 1.0)
# Color gradient used to draw "LED" colors
rr, rg, rb = utils.get_cairo_color_tuple_255_rgb(219, 69, 69)
RED_1 = (0, rr, rg, rb, 1)
RED_2 = (1 - DB_IEC_MINUS_4 - 0.005, rr, rg, rb, 1)
YELLOW_1 = (1 - DB_IEC_MINUS_4 - 0.005 + 0.001, 1, 1, 0, 1)
YELLOW_2 = (1 - DB_IEC_MINUS_12, 1, 1, 0, 1)
gr, gg, gb = utils.get_cairo_color_tuple_255_rgb(86, 188, 137)
GREEN_1 = (1 - DB_IEC_MINUS_12 + 0.001, gr, gg, gb, 1)
GREEN_2 = (1, gr, gg, gb, 1)
LEFT_CHANNEL = "_audio_level.0"
RIGHT_CHANNEL = "_audio_level.1"
MONITORING_AVAILABLE = False
# GUI compoents displaying levels
_monitor_window = None
_master_volume_meter = None
_update_ticker = None
_level_filters = [] # 0 master, 1 - (len - 1) editable tracks
_audio_levels = [] # 0 master, 1 - (len - 1) editable tracks
def init(profile):
audio_level_filter = mlt.Filter(profile, "audiolevel")
global MONITORING_AVAILABLE
if audio_level_filter != None:
MONITORING_AVAILABLE = True
editorstate.audio_monitoring_available = True
else:
MONITORING_AVAILABLE = False
editorstate.audio_monitoring_available = False
global CONTROL_SLOT_H, METER_SLOT_H, METER_LIGHTS, METER_HEIGHT
if editorstate.screen_size_small_height() == True:
if editorstate.SCREEN_HEIGHT > 902:
METER_SLOT_H = 300
CONTROL_SLOT_H = 240
METER_LIGHTS = 100
METER_HEIGHT = METER_LIGHTS * DASH_INK + (METER_LIGHTS - 1) * DASH_SKIP
else:
METER_SLOT_H = 275
CONTROL_SLOT_H = 240
METER_LIGHTS = 82
METER_HEIGHT = METER_LIGHTS * DASH_INK + (METER_LIGHTS - 1) * DASH_SKIP
# We want this to be always present when closing app or we'll need to handle it being missing.
global _update_ticker
_update_ticker = utils.Ticker(_audio_monitor_update, 0.04)
_update_ticker.start_ticker()
_update_ticker.stop_ticker()
def init_for_project_load():
# Monitor window is quaranteed to be closed
if _update_ticker.running:
_update_ticker.stop_ticker()
global _level_filters
_level_filters = None
_init_level_filters(False)
_update_ticker.start_ticker()
def update_mute_states():
if _monitor_window != None:
_monitor_window.update_tracks_mute_states()
def close():
close_audio_monitor()
close_master_meter()
_update_ticker.stop_ticker()
def show_audio_monitor():
global _monitor_window
if _monitor_window != None:
return
_init_level_filters(True)
_monitor_window = AudioMonitorWindow()
global _update_ticker
if _update_ticker.running == False:
_update_ticker.start_ticker()
def close_audio_monitor():
global _monitor_window
if _monitor_window == None:
return
editorstate.PLAYER().stop_playback()
# We're using _monitor_window as a flag here so we need to set to _monitor_window = None
# to stop _audio_monitor_update running before destroying resources used by it
temp_window = _monitor_window
_monitor_window = None
_destroy_level_filters(True)
# Close and destroy window when gtk finds time to do it
GLib.idle_add(_audio_monitor_destroy, temp_window)
def _audio_monitor_destroy(closed_monitor_window):
closed_monitor_window.set_visible(False)
closed_monitor_window.destroy()
return False
def get_master_meter():
_init_level_filters(False)
global _master_volume_meter, _update_ticker
_master_volume_meter = MasterVolumeMeter()
if _update_ticker.running == False:
_update_ticker.start_ticker()
align = guiutils.set_margins(_master_volume_meter.widget, 3, 3, 3, 3)
frame = Gtk.Frame()
frame.add(align)
frame.set_shadow_type(Gtk.ShadowType.ETCHED_OUT)
return frame
def close_master_meter():
global _master_volume_meter
if _master_volume_meter == None:
return
editorstate.PLAYER().stop_playback()
# To avoid crashes we can't actually lose widget object before everything is
# cleaned up well but _master_volume_meter == None is flag for doing audio updates so we must
# set that first
temp_meter = _master_volume_meter
_master_volume_meter = None
_destroy_level_filters(False)
# Close and destroy window when gtk finds time to do it
GLib.idle_add(_master_meter_destroy, temp_meter)
def _master_meter_destroy(closed_master_meter):
closed_master_meter.widget.set_visible(False)
closed_master_meter.widget.destroy()
return False
def _init_level_filters(create_track_filters):
# We're attaching level filters only to MLT objects and adding nothing to python objects,
# so when Sequence is saved these filters will automatically be removed.
# Filters are not part of sequence.Sequence object because they just used for monitoring,
#
# Track/master gain values are persistant, they're also editing desitions
# and are therefore part of sequence.Sequence objects.
# Create levels filters array if it deosn't exist
global _level_filters
if _level_filters == None:
_level_filters = []
seq = editorstate.current_sequence()
# Init master level filter if it does not exist
if len(_level_filters) == 0:
_level_filters.append(_add_audio_level_filter(seq.tractor, seq.profile))
# Init track level filters if requested
if create_track_filters == True:
for i in range(1, len(seq.tracks) - 1):
_level_filters.append(_add_audio_level_filter(seq.tracks[i], seq.profile))
def _destroy_level_filters(destroy_track_filters=False):
global _level_filters, _audio_levels
# We need to be sure that audio level updates are stopped before
# detaching and destroying them
_update_ticker.stop_ticker()
# Detach filters
if len(_level_filters) != 0:
seq = editorstate.current_sequence()
# Only detach master filter if both GUI components destroyed
if _monitor_window == None and _master_volume_meter == None:
seq.tractor.detach(_level_filters[0])
# Track filters are only detached when this called from window close
if destroy_track_filters:
for i in range(1, len(seq.tracks) - 1):
seq.tracks[i].detach(_level_filters[i])
# Destroy unneeded filters
if _master_volume_meter == None and _monitor_window == None:
_level_filters = []
_audio_levels = []
elif _monitor_window == None:
_level_filters = [_level_filters[0]]
_audio_levels[0] = 0.0
if _master_volume_meter != None or _monitor_window != None:
_update_ticker.start_ticker()
def recreate_master_meter_filter_for_new_sequence():
global _level_filters, _audio_levels
# We need to be sure that audio level updates are stopped before
# detaching and destroying them
_update_ticker.stop_ticker()
if len(_level_filters) != 0:
seq = editorstate.current_sequence()
# Only detach master filter if we are displaying audio master meter
if _master_volume_meter != None:
seq.tractor.detach(_level_filters[0])
_level_filters.pop(0)
_audio_levels[0] = 0.0
master_level_filter = _add_audio_level_filter(seq.tractor, seq.profile)
_level_filters.insert(0, master_level_filter)
if _master_volume_meter != None or _monitor_window != None:
_update_ticker.start_ticker()
def _add_audio_level_filter(producer, profile):
audio_level_filter = mlt.Filter(profile, "audiolevel")
mltrefhold.hold_ref(audio_level_filter)
producer.attach(audio_level_filter)
return audio_level_filter
def _audio_monitor_update():
# This is not called from gtk thread
if _monitor_window == None and _master_volume_meter == None:
return
Gdk.threads_enter()
global _audio_levels
_audio_levels = []
for i in range(0, len(_level_filters)):
#print i
audio_level_filter = _level_filters[i]
l_val = _get_channel_value(audio_level_filter, LEFT_CHANNEL)
r_val = _get_channel_value(audio_level_filter, RIGHT_CHANNEL)
_audio_levels.append((l_val, r_val))
if _monitor_window != None:
_monitor_window.meters_area.widget.queue_draw()
if _master_volume_meter != None:
_master_volume_meter.canvas.queue_draw()
Gdk.threads_leave()
def _get_channel_value(audio_level_filter, channel_property):
level_value = audio_level_filter.get(channel_property)
if level_value == None:
level_value = "0.0"
try:
level_float = float(level_value)
except Exception:
level_float = 0.0
return level_float
class AudioMonitorWindow(Gtk.Window):
def __init__(self):
GObject.GObject.__init__(self)
self.connect("delete-event", lambda w, e:close_audio_monitor())
seq = editorstate.current_sequence()
meters_count = 1 + (len(seq.tracks) - 2) # master + editable tracks
self.gain_controls = []
self.meters_area = MetersArea(meters_count)
gain_control_area = Gtk.HBox(False, 0)
seq = editorstate.current_sequence()
for i in range(0, meters_count):
if i == 0:
name = _("Master")
gain = GainControl(name, seq, seq.tractor, True)
else:
name = utils.get_track_name(seq.tracks[i], seq)
gain = GainControl(name, seq, seq.tracks[i])
self.gain_controls.append(gain)
gain_control_area.pack_start(gain, False, False, 0)
meters_frame = Gtk.Frame()
meters_frame.add(self.meters_area.widget)
pane = Gtk.VBox(False, 1)
pane.pack_start(meters_frame, True, True, 0)
pane.pack_start(gain_control_area, True, True, 0)
align = guiutils.set_margins(pane, 12, 12, 4, 4)
# Set pane and show window
self.add(align)
self.set_title(_("Audio Mixer"))
self.show_all()
self.set_resizable(False)
self.set_keep_above(True) # Perhaps configurable later
def update_tracks_mute_states(self):
for i in range(1, len(self.gain_controls)):
self.gain_controls[i].mute_changed()
class MetersArea:
def __init__(self, meters_count):
w = SLOT_W * meters_count
h = METER_SLOT_H
self.widget = cairoarea.CairoDrawableArea2( w,
h,
self._draw)
self.audio_meters = [] # displays both l_Value and r_value
for i in range(0, meters_count):
meter = AudioMeter(METER_HEIGHT)
if i != meters_count - 1:
meter.right_channel.draw_dB = True
self.audio_meters.append(meter)
def _draw(self, event, cr, allocation):
x, y, w, h = allocation
if editorpersistance.prefs.theme == appconsts.LIGHT_THEME:
cr.set_source_rgb(*METER_BG_COLOR)
else:
cr.set_source_rgb(0.0, 0.0, 0.0)
cr.rectangle(0, 0, w, h)
cr.fill()
grad = cairo.LinearGradient (0, Y_TOP_PAD, 0, METER_HEIGHT + Y_TOP_PAD)
grad.add_color_stop_rgba(*RED_1)
grad.add_color_stop_rgba(*RED_2)
grad.add_color_stop_rgba(*YELLOW_1)
grad.add_color_stop_rgba(*YELLOW_2)
grad.add_color_stop_rgba(*GREEN_1)
grad.add_color_stop_rgba(*GREEN_2)
for i in range(0, len(_audio_levels)):
meter = self.audio_meters[i]
l_value, r_value = _audio_levels[i]
x = i * SLOT_W
meter.display_value(cr, x, l_value, r_value, grad)
class AudioMeter:
def __init__(self, height):
self.left_channel = ChannelMeter(height, "L")
self.right_channel = ChannelMeter(height, "R")
self.x_pad_l = 18 + 2
self.x_pad_r = SLOT_W / 2 + 6 - 2
self.meter_width = METER_WIDTH
def set_height(self, h):
self.left_channel.set_height(h)
self.right_channel.set_height(h)
def display_value(self, cr, x, value_left, value_right, grad):
cr.set_source(grad)
cr.set_dash(DASHES, 0)
cr.set_line_width(self.meter_width)
self.left_channel.display_value(cr, x + self.x_pad_l, value_left)
cr.set_source(grad)
cr.set_dash(DASHES, 0)
cr.set_line_width(self.meter_width)
self.right_channel.display_value(cr, x + self.x_pad_r, value_right)
class ChannelMeter:
def __init__(self, height, channel_text):
self.height = height
self.channel_text = channel_text
self.peak = 0.0
self.countdown = 0
self.draw_dB = False
self.dB_x_pad = 11
self.y_top_pad = Y_TOP_PAD
self.over_countdown = 0
def set_height(self, height):
self.height = height
def display_value(self, cr, x, value):
if value > 1.0:
self.over_countdown = OVER_FRAMES
top = self.get_meter_y_for_value(value)
if (self.height - top) < 5: # fix for meter y rounding for vol 0
top = self.height
cr.move_to(x, self.height + self.y_top_pad)
cr.line_to(x, top + self.y_top_pad)
cr.stroke()
if value > self.peak:
self.peak = value
self.countdown = PEAK_FRAMES
if self.peak > value:
if self.peak > 1.0:
self.peak = 1.0
cr.rectangle(x - METER_WIDTH / 2,
self.get_meter_y_for_value(self.peak) + DASH_SKIP * 2 + DASH_INK + 3, # this y is just empirism, works
METER_WIDTH,
DASH_INK)
cr.fill()
self.countdown = self.countdown - 1
if self.countdown <= 0:
self.peak = 0
if self.over_countdown > 0:
cr.set_source_rgb(1,0.6,0.6)
cr.move_to(x, 0)
cr.line_to(x + 4, 4)
cr.line_to(x, 8)
cr.line_to(x - 4, 4)
cr.close_path()
cr.fill()
self.over_countdown = self.over_countdown - 1
self.draw_channel_identifier(cr, x)
if self.draw_dB == True:
self.draw_value_line(cr, x, 1.0, "0", 7)
self.draw_value_line(cr, x, DB_IEC_MINUS_4,"-4", 4)
self.draw_value_line(cr, x, DB_IEC_MINUS_12, "-12", 1)
self.draw_value_line(cr, x, DB_IEC_MINUS_20, "-20", 1)
self.draw_value_line(cr, x, DB_IEC_MINUS_40, "-40", 1)
self.draw_value_line(cr, x, 0.0, "\u221E", 5)
def get_meter_y_for_value(self, value):
y = self.get_y_for_value(value)
# Get pad for y value between "LED"s
dash_sharp_pad = y % (DASH_INK + DASH_SKIP)
# Round to nearest full "LED" using pad value
if dash_sharp_pad < ((DASH_INK + DASH_SKIP) / 2):
meter_y = y - dash_sharp_pad
else:
dash_sharp_pad = (DASH_INK + DASH_SKIP) - dash_sharp_pad
meter_y = y + dash_sharp_pad
return meter_y
def get_y_for_value(self, value):
return self.height - (value * self.height)
def draw_value_line(self, cr, x, value, val_text, x_fine_tune):
y = self.get_y_for_value(value)
self.draw_text(val_text, "Sans 8", cr, x + self.dB_x_pad + x_fine_tune, y - 8 + self.y_top_pad, OVERLAY_COLOR)
def draw_channel_identifier(self, cr, x):
self.draw_text(self.channel_text, "Sans Bold 8", cr, x - 4, self.height + 2 + self.y_top_pad, OVERLAY_COLOR)
def draw_text(self, text, font_desc, cr, x, y, color):
layout = PangoCairo.create_layout(cr)
layout.set_text(text, -1)
desc = Pango.FontDescription(font_desc)
layout.set_font_description(desc)
cr.set_source_rgb(*color)
cr.move_to(x, y)
PangoCairo.update_layout(cr, layout)
PangoCairo.show_layout(cr, layout)
class GainControl(Gtk.Frame):
def __init__(self, name, seq, producer, is_master=False):
GObject.GObject.__init__(self)
self.seq = seq
self.producer = producer
self.is_master = is_master
if is_master:
gain_value = seq.master_audio_gain # tractor master
else:
gain_value = producer.audio_gain # track
gain_value = gain_value * 100
self.adjustment = Gtk.Adjustment(value=gain_value, lower=0, upper=100, step_incr=1)
self.slider = Gtk.VScale()
self.slider.set_adjustment(self.adjustment)
self.slider.set_size_request(SLOT_W - 10, CONTROL_SLOT_H - 105)
self.slider.set_inverted(True)
self.slider.connect("value-changed", self.gain_changed)
if is_master:
pan_value = seq.master_audio_pan
else:
pan_value = producer.audio_pan
if pan_value == appconsts.NO_PAN:
pan_value = 0.5 # center
pan_value = (pan_value - 0.5) * 200 # from range 0 - 1 to range -100 - 100
self.pan_adjustment = Gtk.Adjustment(value=pan_value, lower=-100, upper=100, step_incr=1)
self.pan_slider = Gtk.HScale()
self.pan_slider.set_adjustment(self.pan_adjustment)
self.pan_slider.connect("value-changed", self.pan_changed)
self.pan_button = Gtk.ToggleButton(_("Pan"))
self.pan_button.connect("toggled", self.pan_active_toggled)
if pan_value == 0.0:
self.pan_slider.set_sensitive(False)
else:
self.pan_button.set_active(True)
self.pan_adjustment.set_value(pan_value) # setting button active sets value = 0, set correct value again
label = guiutils.bold_label(name)
vbox = Gtk.VBox(False, 0)
vbox.pack_start(guiutils.get_pad_label(5,5), False, False, 0)
vbox.pack_start(label, False, False, 0)
vbox.pack_start(guiutils.get_pad_label(5,5), False, False, 0)
vbox.pack_start(self.slider, False, False, 0)
vbox.pack_start(self.pan_button, False, False, 0)
vbox.pack_start(self.pan_slider, False, False, 0)
vbox.pack_start(guiutils.get_pad_label(5,5), False, False, 0)
self.add(vbox)
self.set_size_request(SLOT_W, CONTROL_SLOT_H)
self.mute_changed()
def gain_changed(self, slider):
gain = slider.get_value() / 100.0
if self.is_master == True:
self.seq.set_master_gain(gain)
else:
self.seq.set_track_gain(self.producer, gain)
def pan_active_toggled(self, widget):
self.pan_slider.set_value(0.0)
if widget.get_active():
self.pan_slider.set_sensitive(True)
self.seq.add_track_pan_filter(self.producer, 0.5)
if self.is_master:
self.seq.master_audio_pan = 0.5
else:
self.pan_slider.set_sensitive(False)
self.seq.remove_track_pan_filter(self.producer)
if self.is_master:
self.seq.master_audio_pan = appconsts.NO_PAN
def pan_changed(self, slider):
pan_value = (slider.get_value() + 100) / 200.0
if self.is_master:
self.seq.set_master_pan_value(pan_value)
else:
self.seq.set_track_pan_value(self.producer, pan_value)
def mute_changed(self):
if self.is_master == False:
if hasattr(self.producer, "mute_state"):
if self.producer.mute_state == appconsts.TRACK_MUTE_NOTHING or self.producer.mute_state == appconsts.TRACK_MUTE_VIDEO:
self.slider.set_sensitive(True)
self.pan_slider.set_sensitive(True)
self.pan_button.set_sensitive(True)
else:
self.slider.set_sensitive(False)
self.pan_slider.set_sensitive(False)
self.pan_button.set_sensitive(False)
class MasterVolumeMeter:
def __init__(self):
self.H_CUT = 30
self.GRAD_CUT = 37
self.top_pad = 14
self.meter = AudioMeter(METER_HEIGHT + 40)
self.meter.x_pad_l = 6
self.meter.x_pad_r = 14
self.meter.right_channel.draw_dB = True
self.meter.right_channel.dB_x_pad = -14
self.meter.meter_width = 5
self.meter.right_channel.y_top_pad = self.top_pad
self.meter.left_channel.y_top_pad = self.top_pad
w = SLOT_W - 40
h = METER_SLOT_H + 2 + 40
self.canvas = cairoarea.CairoDrawableArea2( w,
h,
self._draw)
self.widget = Gtk.VBox(False, 0)
self.widget.pack_start(self.canvas, True, True, 0)
def _draw(self, event, cr, allocation):
x, y, w, h = allocation
self.meter.set_height(h - self.H_CUT)
if editorpersistance.prefs.theme == appconsts.LIGHT_THEME:
cr.set_source_rgb(*METER_BG_COLOR)
else:
cr.set_source_rgb(0.0, 0.0, 0.0)
cr.fill_preserve()
cr.rectangle(0, 0, w, h)
cr.fill()
grad = cairo.LinearGradient (0, self.top_pad, 0, self.top_pad + h - self.GRAD_CUT)
grad.add_color_stop_rgba(*RED_1)
grad.add_color_stop_rgba(*RED_2)
grad.add_color_stop_rgba(*YELLOW_1)
grad.add_color_stop_rgba(*YELLOW_2)
grad.add_color_stop_rgba(*GREEN_1)
grad.add_color_stop_rgba(*GREEN_2)
l_value, r_value = _audio_levels[0]
x = 0
self.meter.display_value(cr, x, l_value, r_value, grad)
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/audiosync.py 0000664 0000000 0000000 00000035152 13610327166 0025461 0 ustar 00root root 0000000 0000000 """
Flowblade Movie Editor is a nonlinear video editor.
Copyright 2012 Janne Liljeblad.
This file is part of Flowblade Movie Editor .
Flowblade Movie Editor 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.
Flowblade Movie Editor is distributed in the hope that 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 Flowblade Movie Editor. If not, see .
"""
"""
Application module.
Handles application initialization, shutdown, opening projects, autosave and changing
sequences.
"""
import datetime
import hashlib
import mlt
import os
import subprocess
import sys
import time
import threading
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('PangoCairo', '1.0')
from gi.repository import GLib
from gi.repository import Gtk, Gdk
import appconsts
import clapperless
import dialogs
import dialogutils
import edit
import editorpersistance
import editorstate
from editorstate import PROJECT
from editorstate import current_sequence
import gui
import movemodes
import projectaction
import renderconsumer
import respaths
import tlinewidgets
import updater
import userfolders
import utils
_tline_sync_data = None # Compound clip and tline clip sync functions can't pass the same data througn clapperless so
# we use this global to save data as needed for tline sync function.
# The data flow is a bit haed to follow here, this needs tobe refactored.
_compare_dialog_thread = None
class ClapperlesLaunchThread(threading.Thread):
def __init__(self, video_file, audio_file, completed_callback):
threading.Thread.__init__(self)
self.video_file = video_file
self.audio_file = audio_file
self.completed_callback = completed_callback
def run(self):
_write_offsets(self.video_file, self.audio_file, self.completed_callback)
# ------------------------------------------------------- module funcs
def _write_offsets(video_file_path, audio_file_path, completed_callback):
print("Starting clapperless analysis...")
fps = str(int(utils.fps() + 0.5))
idstr = _get_offset_file_idstr(video_file_path, audio_file_path)
FLOG = open(userfolders.get_cache_dir() + "log_clapperless", 'w')
# clapperless.py computes offsets and writes them to file clapperless.OFFSETS_DATA_FILE
p = subprocess.Popen([sys.executable, respaths.LAUNCH_DIR + "flowbladeclapperless", video_file_path, audio_file_path, "--rate", fps, "--idstr", idstr], stdin=FLOG, stdout=FLOG, stderr=FLOG)
p.wait()
# Offsets are now available
GLib.idle_add(completed_callback, (video_file_path, audio_file_path, idstr))
def _get_offset_file_idstr(file_1, file_2):
return hashlib.md5((file_1 + file_2).encode('utf-8')).hexdigest()
def _read_offsets(idstr):
offsets_file = userfolders.get_cache_dir() + clapperless.OFFSETS_DATA_FILE + "_"+ idstr
with open(offsets_file) as f:
file_lines = f.readlines()
file_lines = [x.rstrip("\n") for x in file_lines]
_files_offsets = {}
for line in file_lines:
tokens = line.split(clapperless.MAGIC_SEPARATOR)
_files_offsets[tokens[0]] = tokens[1]
os.remove(offsets_file)
return _files_offsets
# ------------------------------------------------------- tline audio sync
def init_select_tline_sync_clip(popup_data):
clip, track, item_id, x = popup_data
frame = tlinewidgets.get_frame(x)
clip_index = current_sequence().get_clip_index(track, frame)
if not (track.clips[clip_index] == clip):
# This should never happen
print("big fu at init_select_tline_sync_clip(...)")
return
gdk_window = gui.tline_display.get_parent_window();
gdk_window.set_cursor(Gdk.Cursor.new(Gdk.CursorType.TCROSS))
editorstate.edit_mode = editorstate.SELECT_TLINE_SYNC_CLIP
global _tline_sync_data
_tline_sync_data = TLineSyncData()
_tline_sync_data.origin_clip = clip
_tline_sync_data.origin_track = track
_tline_sync_data.origin_clip_index = clip_index
def select_sync_clip_mouse_pressed(event, frame):
sync_clip = _get_sync_tline_clip(event, frame)
if sync_clip == None:
return # selection wasn't good
if utils.is_mlt_xml_file(sync_clip.path) == True:
# This isn't translated because 1.14 translation window is close, translation coming for 1.16
dialogutils.warning_message(_("Cannot Timeline Audio Sync with Compound Clips!"),
_("Audio syncing for Compound Clips is not supported."),
gui.editor_window.window,
True)
return
sync_track = tlinewidgets.get_track(event.y)
sync_clip_index = sync_track.clips.index(sync_clip)
_tline_sync_data.sync_clip = sync_clip
_tline_sync_data.sync_track = sync_track
_tline_sync_data.sync_clip_index = sync_clip_index
# TImeline media offset for clips
sync_clip_start_in_tline = sync_track.clip_start(sync_clip_index)
_tline_sync_data.origin_clip_start_in_tline = _tline_sync_data.origin_track.clip_start(_tline_sync_data.origin_clip_index)
_tline_sync_data.clip_tline_media_offset = (sync_clip_start_in_tline - sync_clip.clip_in) - (_tline_sync_data.origin_clip_start_in_tline - _tline_sync_data.origin_clip.clip_in)
gdk_window = gui.tline_display.get_parent_window();
gdk_window.set_cursor(Gdk.Cursor.new(Gdk.CursorType.LEFT_PTR))
global _compare_dialog_thread
_compare_dialog_thread = AudioCompareActiveThread()
_compare_dialog_thread.start()
# This or GUI freezes, we really can't do Popen.wait() in a Gtk thread
clapperless_thread = ClapperlesLaunchThread(_tline_sync_data.origin_clip.path, sync_clip.path, _tline_sync_offsets_computed_callback)
clapperless_thread.start()
# Edit consumes selection
movemodes.clear_selected_clips()
updater.repaint_tline()
def _get_sync_tline_clip(event, frame):
sync_track = tlinewidgets.get_track(event.y)
if sync_track == None:
return None
if sync_track == _tline_sync_data.origin_track:
dialogutils.warning_message(_("Audio Sync parent clips must be on differnt tracks "),
_("Selected audio sync clip is on the sametrack as the sync action origin clip."),
gui.editor_window.window,
True)
return None
sync_clip_index = current_sequence().get_clip_index(sync_track, frame)
if sync_clip_index == -1:
return None
return sync_track.clips[sync_clip_index]
def _tline_sync_offsets_computed_callback(clapperless_data):
print("Clapperless done for tline sync")
global _compare_dialog_thread
_compare_dialog_thread.compare_done()
file_path_1, file_path_2, idstr = clapperless_data
files_offsets = _read_offsets(idstr)
_tline_sync_data.media_offset_frames = int(float(files_offsets[file_path_2]) + 0.5)
dialogs.tline_audio_sync_dialog(_tline_audio_sync_dialog_callback, _tline_sync_data)
def _tline_audio_sync_dialog_callback(dialog, response_id, data):
if response_id != Gtk.ResponseType.ACCEPT:
dialog.destroy()
return
dialog.destroy()
sync_move_frames = _tline_sync_data.clip_tline_media_offset - _tline_sync_data.media_offset_frames
over_in = _tline_sync_data.origin_clip_start_in_tline + sync_move_frames
over_out = over_in + (_tline_sync_data.origin_clip.clip_out - _tline_sync_data.origin_clip.clip_in) + 1
# We're not not supporting case where clip would start before timeline start.
if over_in < 0:
primary_txt = _("Audio sync move not possible")
secondary_txt = _("Clip starts ") + str(over_in) + _(" frames before timeline start if it is moved \nto be in audio sync with the specified clip.\n\n") + \
_("You need to move forward or shorten the clips in question to make the operation succeed.")
dialogutils.info_message(primary_txt, secondary_txt, gui.editor_window.window)
return
data = {"track":_tline_sync_data.origin_track,
"over_in":over_in,
"over_out":over_out,
"selected_range_in":_tline_sync_data.origin_clip_index,
"selected_range_out":_tline_sync_data.origin_clip_index,
"move_edit_done_func":None}
action = edit.overwrite_move_action(data)
action.do_edit()
class TLineSyncData:
def __init__(self):
# Origin clip
self.origin_clip = None
self.origin_track = None
self.origin_clip_index = None
# Clip to sync origin clip
self.sync_clip = None
self.sync_track = None
self.sync_clip_index = None
# Timeline data
self.origin_clip_start_in_tline = None
self.clip_tline_media_offset = None
# Media offset from clapperless
self.media_offset_frames = None
# ------------------------------------------------------- compound clip audio sync
def create_audio_sync_compound_clip():
selection = gui.media_list_view.get_selected_media_objects()
if len(selection) != 2:
return
video_file = selection[0].media_file
audio_file = selection[1].media_file
# Can't sync coumpound clips
if utils.is_mlt_xml_file(video_file.path) == True or utils.is_mlt_xml_file(audio_file.path) == True:
# This isn't translated because 1.14 translation window is close, translation coming for 1.16
dialogutils.warning_message(_("Cannot Create Audio Sync Compound Clip from Compound Clips!"),
_("Audio syncing Compound Clips is not supported."),
gui.editor_window.window,
True)
return
# Can't sync 2 audio clips
if video_file.type == appconsts.AUDIO and audio_file.type == appconsts.AUDIO:
# This isn't translated because 1.14 translation window is close, translation coming for 1.16
dialogutils.warning_message(_("Cannot Create Audio Sync Compound Clip from 2 Audio Clips!"),
_("One of the media items needs to be a video clip."),
gui.editor_window.window,
True)
return
if video_file.type == appconsts.VIDEO and audio_file.type == appconsts.AUDIO:
pass
elif video_file.type == appconsts.AUDIO and audio_file.type == appconsts.VIDEO:
video_file, audio_file = audio_file, video_file
else:
print("2 video files, video audio assignments determined by selection order")
global _compare_dialog_thread
_compare_dialog_thread = AudioCompareActiveThread()
_compare_dialog_thread.start()
# This or GUI freezes, we really can't do Popen.wait() in a Gtk thread
clapperless_thread = ClapperlesLaunchThread(video_file.path, audio_file.path, _compound_offsets_complete)
clapperless_thread.start()
def _compound_offsets_complete(data):
print("Clapperless done for compound clip")
global _compare_dialog_thread
_compare_dialog_thread.compare_done()
video_file_path, audio_file_path, idstr = data
files_offsets = _read_offsets(idstr)
sync_data = (files_offsets, data)
# lets's just set default name to something unique-ish
default_name = _("SYNC_CLIP_") + str(datetime.date.today()) + "_" + time.strftime("%H%M%S") + ".xml"
dialogs.compound_clip_name_dialog(_do_create_sync_compound_clip, default_name, _("Save Sync Compound Clip XML"), sync_data)
def _do_create_sync_compound_clip(dialog, response_id, data):
if response_id != Gtk.ResponseType.ACCEPT:
dialog.destroy()
return
sync_data, name_entry = data
files_offsets, clips = sync_data
video_file, audio_file, idstr = clips
media_name = name_entry.get_text()
dialog.destroy()
# Create unique file path in hidden render folder
folder = userfolders.get_render_dir()
uuid_str = hashlib.md5(str(os.urandom(32)).encode('utf-8')).hexdigest()
write_file = folder + "/"+ uuid_str + ".xml"
# Create tractor
tractor = mlt.Tractor()
multitrack = tractor.multitrack()
track_video = mlt.Playlist()
track_audio = mlt.Playlist()
track_audio.set("hide", 1) # video off, audio on as mlt "hide" value
multitrack.connect(track_audio, 0)
multitrack.connect(track_video, 0)
# Create clips
video_clip = mlt.Producer(PROJECT().profile, str(video_file))
audio_clip = mlt.Producer(PROJECT().profile, str(audio_file))
# Get offset
offset = float(files_offsets[audio_file])
print(audio_file, offset)
# Add clips
if offset > 0:
offset_frames = int(float(offset) + 0.5)
track_video.append(video_clip, 0, video_clip.get_length() - 1)
track_audio.insert_blank(0, offset_frames)
track_audio.append(audio_clip, 0, audio_clip.get_length() - 1)
elif offset < 0:
offset_frames = int(float(offset) - 0.5)
track_video.insert_blank(0, offset_frames)
track_video.append(video_clip, 0, video_clip.get_length() - 1)
track_audio.append(audio_clip, 0, audio_clip.get_length() - 1)
else:
track_video.append(video_clip, 0, video_clip.get_length() - 1)
track_audio.append(audio_clip, 0, audio_clip.get_length() - 1)
# render MLT XML, callback in projectaction.py creates media object
render_player = renderconsumer.XMLCompoundRenderPlayer(write_file, media_name, projectaction._xml_compound_render_done_callback, tractor)
render_player.start()
class AudioCompareActiveThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.running = True
def run(self):
Gdk.threads_enter()
dialog = dialogs.audio_sync_active_dialog()
dialog.progress_bar.set_pulse_step(0.2)
time.sleep(0.1)
Gdk.threads_leave()
while self.running:
dialog.progress_bar.pulse()
time.sleep(0.2)
PROJECT().update_media_lengths_on_load = False
Gdk.threads_enter()
dialog.destroy()
Gdk.threads_leave()
def compare_done(self):
self.running = False
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/audiowaveform.py 0000664 0000000 0000000 00000017104 13610327166 0026330 0 ustar 00root root 0000000 0000000 """
Flowblade Movie Editor is a nonlinear video editor.
Copyright 2012 Janne Liljeblad.
This file is part of Flowblade Movie Editor .
Flowblade Movie Editor 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.
Flowblade Movie Editor is distributed in the hope that 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 Flowblade Movie Editor. If not, see .
"""
"""
Modules handles creating and caching audio waveform images for clips.
"""
import mlt
import os
import pickle
import threading
import time
from gi.repository import Gtk, Gdk
import appconsts
import atomicfile
import dialogutils
from editorstate import PROJECT
import gui
import guiutils
import updater
import userfolders
import utils
# Frame level value cache for audio levels
# path -> list of frame levels
frames_cache = {}
waveform_thread = None
LEFT_CHANNEL = "_audio_level.0"
RIGHT_CHANNEL = "_audio_level.1"
# ------------------------------------------------- waveforms
def set_waveform_displayer_clip_from_popup(data):
clip, track, item_id, item_data = data
global frames_cache
if clip.path in frames_cache:
frame_levels = frames_cache[clip.path]
clip.waveform_data = frame_levels
updater.repaint_tline()
return
cache_file_path = userfolders.get_cache_dir() + appconsts.AUDIO_LEVELS_DIR + _get_unique_name_for_media(clip.path)
if os.path.isfile(cache_file_path):
frame_levels = utils.unpickle(cache_file_path)
frames_cache[clip.path] = frame_levels
clip.waveform_data = frame_levels
updater.repaint_tline()
return
progress_bar = Gtk.ProgressBar()
title = _("Audio Levels Data Render")
text = "Media File: " + clip.path
dialog = _waveform_render_progress_dialog(_waveform_render_abort, title, text, progress_bar, gui.editor_window.window)
dialog.progress_bar = progress_bar
global waveform_thread
waveform_thread = WaveformCreator(clip, track.height, dialog)
waveform_thread.start()
def _waveform_render_abort(dialog, response_id):
if waveform_thread != None:
waveform_thread.abort_rendering()
def _waveform_render_stop(dialog, response_id):
global waveform_thread
waveform_thread = None
dialogutils.delay_destroy_window(dialog, 1.6)
def clear_waveform(data):
# LOOK TO REMOVE; DOES NOT SEEMS CURRENT
clip, track, item_id, item_data = data
clip.waveform_data = None
clip.waveform_data_frame_height = -1
updater.repaint_tline()
def _get_unique_name_for_media(media_file_path):
return utils.get_unique_name_for_audio_levels_file(media_file_path, PROJECT().profile)
class WaveformCreator(threading.Thread):
def __init__(self, clip, track_height, dialog):
threading.Thread.__init__(self)
self.clip = clip
self.temp_clip = self._get_temp_producer(clip)
self.file_cache_path = userfolders.get_cache_dir() + appconsts.AUDIO_LEVELS_DIR + _get_unique_name_for_media(clip.path)
self.track_height = track_height
self.abort = False
self.clip_media_length = self.temp_clip.get_length()
self.last_rendered_frame = 0
self.stopped = False
self.dialog = dialog
def run(self):
global frames_cache
frame_levels = [None] * self.clip_media_length
frames_cache[self.clip.path] = frame_levels
Gdk.threads_enter()
self.dialog.progress_bar.set_fraction(0.0)
self.dialog.progress_bar.set_text(str(0) + "%")
while(Gtk.events_pending()):
Gtk.main_iteration()
Gdk.threads_leave()
time.sleep(0.2)
for frame in range(0, len(frame_levels)):
if self.abort:
break
self.temp_clip.seek(frame)
mlt.frame_get_waveform(self.temp_clip.get_frame(), 10, 50)
val = self.levels.get(RIGHT_CHANNEL)
if val == None:
val = 0.0
frame_levels[frame] = float(val)
self.last_rendered_frame = frame
if frame % 500 == 0:
render_fraction = float(self.last_rendered_frame) / float(self.clip_media_length)
Gdk.threads_enter()
self.dialog.progress_bar.set_fraction(render_fraction)
pros = int(render_fraction * 100)
self.dialog.progress_bar.set_text(str(pros) + "%")
while(Gtk.events_pending()):
Gtk.main_iteration()
Gdk.threads_leave()
time.sleep(0.1)
if not self.abort:
self.clip.waveform_data = frame_levels
with atomicfile.AtomicFileWriter(self.file_cache_path, "wb") as afw:
write_file = afw.get_file()
pickle.dump(frame_levels, write_file)
Gdk.threads_enter()
self.dialog.progress_bar.set_fraction(1.0)
self.dialog.progress_bar.set_text(_("Saving to Hard Drive"))
Gdk.threads_leave()
else:
frames_cache.pop(self.clip.path, None)
updater.repaint_tline()
# Set thread ref to None to flag that no waveforms are being created
global waveform_thread
waveform_thread = None
_waveform_render_stop(self.dialog, None)
def _get_temp_producer(self, clip):
service = clip.get("mlt_service")
if service.startswith("xml"):
service = "xml-nogl"
temp_producer = mlt.Producer(PROJECT().profile, service, clip.get("resource"))
channels = mlt.Filter(PROJECT().profile, "audiochannels")
converter = mlt.Filter(PROJECT().profile, "audioconvert")
self.levels = mlt.Filter(PROJECT().profile, "audiolevel")
temp_producer.attach(channels)
temp_producer.attach(converter)
temp_producer.attach(self.levels)
temp_producer.path = clip.path
return temp_producer
def abort_rendering(self):
self.abort = True
def _waveform_render_progress_dialog(callback, title, text, progress_bar, parent_window):
dialog = Gtk.Dialog(title,
parent_window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Cancel"), Gtk.ResponseType.REJECT))
dialog.text_label = Gtk.Label(label=text)
dialog.text_label.set_use_markup(True)
text_box = Gtk.HBox(False, 2)
text_box.pack_start(dialog.text_label,False, False, 0)
text_box.pack_start(Gtk.Label(), True, True, 0)
status_box = Gtk.HBox(False, 2)
status_box.pack_start(text_box, False, False, 0)
status_box.pack_start(Gtk.Label(), True, True, 0)
progress_vbox = Gtk.VBox(False, 2)
progress_vbox.pack_start(status_box, False, False, 0)
progress_vbox.pack_start(guiutils.get_pad_label(10, 10), False, False, 0)
progress_vbox.pack_start(progress_bar, False, False, 0)
alignment = guiutils.set_margins(progress_vbox, 12, 12, 12, 12)
dialog.vbox.pack_start(alignment, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
dialog.set_default_size(500, 125)
alignment.show_all()
dialog.connect('response', callback)
dialog.show()
return dialog
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/audiowaveformrenderer.py 0000664 0000000 0000000 00000020110 13610327166 0030046 0 ustar 00root root 0000000 0000000 """
Flowblade Movie Editor is a nonlinear video editor.
Copyright 2012 Janne Liljeblad.
This file is part of Flowblade Movie Editor .
Flowblade Movie Editor 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.
Flowblade Movie Editor is distributed in the hope that 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 Flowblade Movie Editor. If not, see .
"""
"""
Modules handles creating and caching audio waveform images for clips.
"""
import locale
import mlt
import os
import pickle
import subprocess
import sys
import threading
import gi
gi.require_version('Gdk', '3.0')
from gi.repository import Gdk
import appconsts
import atomicfile
import editorpersistance
import editorstate
import mltenv
import mltprofiles
import mlttransitions
import mltfilters
import processutils
import renderconsumer
import respaths
import translations
import updater
import userfolders
import utils
LEFT_CHANNEL = "_audio_level.0"
RIGHT_CHANNEL = "_audio_level.1"
FILE_SEPARATOR = "#file:"
_waveforms = {} # Memory cache for waveform data
_queued_waveform_renders = [] # Media queued for render during one timeline repaint
_render_already_requested = [] # Files that have been sent to rendering since last project load
# ------------------------------------------------- waveform cache
def clear_cache():
global _waveforms, _queued_waveform_renders, _render_already_requested
_waveforms = {}
_queued_waveform_renders = []
_render_already_requested = []
def get_waveform_data(clip):
# Return from memory if present
global _waveforms
try:
waveform = _waveforms[clip.path]
return waveform
except:
pass
# Load from disk if found, otherwise queue for levels render
levels_file_path = _get_levels_file_path(clip.path, editorstate.PROJECT().profile)
if os.path.isfile(levels_file_path):
if os.path.getsize(levels_file_path) == 0:
print( "Size zero Audio levels file, this is error!", levels_file_path)
waveform = utils.unpickle(levels_file_path)
_waveforms[clip.path] = waveform
return waveform
else:
global _queued_waveform_renders
_queued_waveform_renders.append(clip.path)
return None
# ------------------------------------------------- launching render
def launch_queued_renders():
# Render files that were not found when timeline was displayed
global _queued_waveform_renders
if len(_queued_waveform_renders) == 0:
return
launch_audio_levels_rendering(_queued_waveform_renders)
_queued_waveform_renders = []
def launch_audio_levels_rendering(file_names):
# Only render audio levels for media that does not have existing levels file
rendered_media = ""
for media_file in file_names:
levels_file_path = _get_levels_file_path(media_file, editorstate.PROJECT().profile)
if os.path.isfile(levels_file_path):
continue
else:
global _render_already_requested
if not (media_file in _render_already_requested):
_render_already_requested.append(media_file)
rendered_media = rendered_media + FILE_SEPARATOR + media_file
if rendered_media == "":
return
profile_desc = editorstate.PROJECT().profile_desc
# This is called from GTK thread, so we need to launch process from another thread to
# clean-up properly and not block GTK thread/GUI
global single_render_launch_thread
single_render_launch_thread = AudioRenderLaunchThread(rendered_media, profile_desc)
single_render_launch_thread.start()
def _get_levels_file_path(media_file_path, profile):
return userfolders.get_cache_dir() + appconsts.AUDIO_LEVELS_DIR + utils.get_unique_name_for_audio_levels_file(media_file_path, profile)
class AudioRenderLaunchThread(threading.Thread):
def __init__(self, rendered_media, profile_desc):
threading.Thread.__init__(self)
self.rendered_media = rendered_media
self.profile_desc = profile_desc
def run(self):
# Launch render process and wait for it to end
FLOG = open(userfolders.get_cache_dir() + "log_audio_levels_render", 'w')
# Sep-2018 - SvdB - Added self. to be able to access the thread through 'process'
self.process = subprocess.Popen([sys.executable, respaths.LAUNCH_DIR + "flowbladeaudiorender", \
self.rendered_media, self.profile_desc, respaths.ROOT_PATH], \
stdin=FLOG, stdout=FLOG, stderr=FLOG)
self.process.wait()
Gdk.threads_enter()
updater.repaint_tline()
Gdk.threads_leave()
# --------------------------------------------------------- rendering
def main():
# Set paths.
root_path = sys.argv[3]
respaths.set_paths(root_path)
try:
editorstate.mlt_version = mlt.LIBMLT_VERSION
except:
editorstate.mlt_version = "0.0.99" # magic string for "not found"
# Set folders paths
userfolders.init()
# Load editor prefs and list of recent projects
editorpersistance.load()
# Init translations module with translations data
translations.init_languages()
translations.load_filters_translations()
mlttransitions.init_module()
repo = mlt.Factory().init()
processutils.prepare_mlt_repo(repo)
# Set numeric locale to use "." as radix, MLT initilizes this to OS locale and this causes bugs
locale.setlocale(locale.LC_NUMERIC, 'C')
# Check for codecs and formats on the system
mltenv.check_available_features(repo)
renderconsumer.load_render_profiles()
# Load filter and compositor descriptions from xml files.
mltfilters.load_filters_xml(mltenv.services)
mlttransitions.load_compositors_xml(mltenv.transitions)
# Create list of available mlt profiles
mltprofiles.load_profile_list()
profile_desc = sys.argv[2]
profile = mltprofiles.get_profile(profile_desc)
files_paths = sys.argv[1]
files_paths = files_paths.lstrip(FILE_SEPARATOR)
files = files_paths.split(FILE_SEPARATOR)
for f in files:
t = WaveformCreator(f, profile_desc)
t.start()
t.join()
class WaveformCreator(threading.Thread):
def __init__(self, clip_path, profile_desc):
threading.Thread.__init__(self)
self.clip_path = clip_path
profile = mltprofiles.get_profile(profile_desc)
self.temp_clip = self._get_temp_producer(clip_path, profile)
self.file_cache_path =_get_levels_file_path(clip_path, profile)
self.last_rendered_frame = 0
def run(self):
frame_levels = [None] * self.clip_media_length
for frame in range(0, len(frame_levels)):
self.temp_clip.seek(frame)
mlt.frame_get_waveform(self.temp_clip.get_frame(), 10, 50)
val = self.levels.get(RIGHT_CHANNEL)
if val == None:
val = 0.0
frame_levels[frame] = float(val)
self.last_rendered_frame = frame
with atomicfile.AtomicFileWriter(self.file_cache_path, "wb") as afw:
write_file = afw.get_file()
pickle.dump(frame_levels, write_file)
def _get_temp_producer(self, clip_path, profile):
temp_producer = mlt.Producer(profile, str(clip_path))
channels = mlt.Filter(profile, "audiochannels")
converter = mlt.Filter(profile, "audioconvert")
self.levels = mlt.Filter(profile, "audiolevel")
temp_producer.attach(channels)
temp_producer.attach(converter)
temp_producer.attach(self.levels)
temp_producer.path = clip_path
self.clip_media_length = temp_producer.get_length()
return temp_producer
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/boxmove.py 0000664 0000000 0000000 00000032423 13610327166 0025140 0 ustar 00root root 0000000 0000000 """
Flowblade Movie Editor is a nonlinear video editor.
Copyright 2012 Janne Liljeblad.
This file is part of Flowblade Movie Editor .
Flowblade Movie Editor 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.
Flowblade Movie Editor is distributed in the hope that 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 Flowblade Movie Editor. If not, see .
"""
"""
Handles Box tool functionality.
"""
import appconsts
import dialogutils
import gui
import edit
import editorstate
from editorstate import current_sequence
import tlinewidgets
import updater
box_selection_data = None
edit_data = None
entered_from_overwrite = False
def clear_data():
# These need to cleared when box tool is activated
global box_selection_data, edit_data
box_selection_data = None
edit_data = None
def mouse_press(event, frame):
global edit_data, box_selection_data
if box_selection_data == None: # mouse action is to select
press_point = (event.x, event.y)
edit_data = {"action_on":True,
"press_point":press_point,
"mouse_point":press_point,
"box_selection_data":None}
else: # mouse action is to move
if box_selection_data.is_hit(event.x, event.y) == False:
# Back to start state if selection box missed
edit_data = None
box_selection_data = None
else:
edit_data = {"action_on":True,
"press_frame":frame,
"delta":0,
"box_selection_data":box_selection_data}
tlinewidgets.set_edit_mode(edit_data, tlinewidgets.draw_overwrite_box_overlay)
updater.repaint_tline()
def mouse_move(x, y, frame):
global edit_data
if edit_data == None:
return
if box_selection_data == None: # mouse action is to select
edit_data["mouse_point"] = (x, y)
else: # mouse action is to move
delta = frame - edit_data["press_frame"]
edit_data["delta"] = delta
tlinewidgets.set_edit_mode_data(edit_data)
updater.repaint_tline()
def mouse_release(x, y, frame):
global box_selection_data, edit_data, entered_from_overwrite
if edit_data == None:
if entered_from_overwrite == True:
_exit_to_overwrite()
return
if box_selection_data == None: # mouse action is to select
box_selection_data = BoxMoveData(edit_data["press_point"], (x, y))
locked_track = box_selection_data.get_possible_locked_track()
if locked_track != None:
dialogutils.track_lock_check_and_user_info(locked_track)
edit_data = None
box_selection_data = None
tlinewidgets.set_edit_mode_data(edit_data)
updater.repaint_tline()
# Exit box mode if entered from overwrite
if entered_from_overwrite == True:
_exit_to_overwrite()
return
if box_selection_data.is_empty() == False:
edit_data = {"action_on":True,
"press_frame":frame,
"delta":0,
"box_selection_data":box_selection_data}
else:
box_selection_data = None
edit_data = {"action_on":False,
"press_frame":-1,
"delta":0,
"box_selection_data":box_selection_data}
# Exit box mode if entered from overwrite with empty selection
if entered_from_overwrite == True:
_exit_to_overwrite()
return
else: # mouse action is to move
# Exit if selection contains locked track
locked_track = box_selection_data.get_possible_locked_track()
if locked_track != None:
dialogutils.track_lock_check_and_user_info(locked_track)
edit_data = None
box_selection_data = None
tlinewidgets.set_edit_mode_data(edit_data)
updater.repaint_tline()
# Exit box mode if entered from overwrite
if entered_from_overwrite == True:
_exit_to_overwrite()
return
# If we lock track after
delta = frame - edit_data["press_frame"]
edit_data["delta"] = delta
# Do edit
data = {"box_selection_data":box_selection_data,
"delta":delta}
action = edit.box_overwrite_move_action(data)
action.do_edit()
# Back to start state
edit_data = None
box_selection_data = None
# Exit box mode if entered from overwrite with empty selection
if entered_from_overwrite == True:
_exit_to_overwrite()
return
tlinewidgets.set_edit_mode_data(edit_data)
updater.repaint_tline()
def _exit_to_overwrite():
# If we entered box mode from overwite mode empty click, this is used to enter back into overwrite mode.
global entered_from_overwrite
entered_from_overwrite = False
editorstate.overwrite_mode_box = False
tlinewidgets.set_edit_mode_data(None)
gui.editor_window.set_cursor_to_mode() # This gets set wrong in editevent.tline_canvas_mouse_released() and were putting it back here,
# this could be investigated for better solution, this could cause a cursor flash, but on dev system we're not getting it.
updater.repaint_tline()
class BoxMoveData:
"""
This class collects data needed for Box tool edits.
"""
def __init__(self, p1, p2):
self.topleft_frame = -1
self.topleft_track = -1
self.width_frames = -1
self.height_tracks = -1
self.track_selections = []
self.selected_compositors = []
self._get_selection_data(p1, p2)
def _get_selection_data(self, p1, p2):
x1, y1 = p1
x2, y2 = p2
if x1 > x2:
x1, x2 = x2, x1
if y1 > y2:
y1, y2 = y2, y1
start_frame = tlinewidgets.get_frame(x1)
end_frame = tlinewidgets.get_frame(x2)
track_top_index = self.get_bounding_track_index(y1, tlinewidgets.get_track(y1))
track_bottom_index = self.get_bounding_track_index(y2, tlinewidgets.get_track(y2))
self.topleft_track = track_top_index - 1
# Get compositors
for i in range(track_bottom_index + 1, track_top_index):
track_compositors = current_sequence().get_track_compositors(i)
for comp in track_compositors:
if comp.clip_in >= start_frame and comp.clip_out < end_frame:
self.selected_compositors.append(comp)
# Get BoxTrackSelection objects
for i in range(track_bottom_index + 1, track_top_index):
self.track_selections.append(BoxTrackSelection(i, start_frame, end_frame))
# Drop empty tracks from bottom up
while len(self.track_selections) > 0:
if self.track_selections[0].is_empty() == True:
self.track_selections.pop(0)
else:
track_bottom_index = self.track_selections[0].track_id
break
# Drop empty tracks from top down
while len(self.track_selections) > 0:
if self.track_selections[-1].is_empty() == True:
self.track_selections.pop(-1)
else:
self.topleft_track = self.track_selections[-1].track_id
break
self.height_tracks = self.topleft_track - track_bottom_index + 1# self.topleft_track is inclusive to height, track_bottom_index is eclusive to height
# Get selection bounding box
self.topleft_frame = 1000000000000
for track_selection in self.track_selections:
if track_selection.range_frame_in != -1:
if track_selection.range_frame_in < self.topleft_frame:
self.topleft_frame = track_selection.range_frame_in
last_frame = 0
for track_selection in self.track_selections:
if track_selection.range_frame_out != -1:
if track_selection.range_frame_out > last_frame:
last_frame = track_selection.range_frame_out
self.width_frames = last_frame - self.topleft_frame
def get_bounding_track_index(self, mouse_y, tline_track):
if tline_track == None:
if mouse_y < tlinewidgets.REF_LINE_Y:
return len(current_sequence().tracks) # mouse pressed above all tracks
else:
return 0 # mouse pressed below all tracks
else:
return tline_track.id
def is_empty(self):
if len(self.track_selections) == 0:
return True
return False
def is_hit(self, x, y):
hit_frame = tlinewidgets.get_frame(x)
hit_track = tlinewidgets.get_track(y).id
if ((hit_frame >= self.topleft_frame and hit_frame < self.topleft_frame + self.width_frames) and
(hit_track <= self.topleft_track and hit_track > self.topleft_track - self.height_tracks)):
return True
return False
def get_possible_locked_track(self):
for selection in self.track_selections:
if current_sequence().tracks[selection.track_id].edit_freedom == appconsts.LOCKED:
return current_sequence().tracks[selection.track_id]
return None
class BoxTrackSelection:
"""
This class collects data on track's Box selected clips.
"""
def __init__(self, i, start_frame, end_frame):
self.track_id = i
self.selected_range_in = -1
self.selected_range_out = -1
self.range_frame_in = -1
self.range_frame_out = -1
self.clip_lengths = []
self.clip_is_media = []
track = editorstate.current_sequence().tracks[i]
# Get start range index, outer selection required
start_bound_index = editorstate.current_sequence().get_clip_index(track, start_frame)
if start_bound_index == -1:
return # Selection starts after end of track contents, selection is empty
if start_bound_index != 0:
self.selected_range_in = start_bound_index + 1
if self.selected_range_in == len(current_sequence().tracks):
return # box selection was on last clip, nothing is elected
else:
if start_frame == 0:
self.selected_range_in = 0 # first clip on timeline can be selected by selecting frame 0, no outer selection required here
else:
self.selected_range_in = start_bound_index + 1
if self.selected_range_in == len(current_sequence().tracks):
return # box selection was on last clip, nothing is elected
# Get end range index, outer selection required
end_bound_index = editorstate.current_sequence().get_clip_index(track, end_frame)
if end_bound_index != -1:
self.selected_range_out = end_bound_index - 1
if self.selected_range_out < 0:
return # range end was on first clip, nothing was selected
else:
if self.selected_range_in == -1:
return # track is empty
# Range ends on last clip
self.selected_range_out = len(track.clips) - 1
# Drop blanks from start
blanks_stripped_start = self.selected_range_in
for i in range(self.selected_range_in, self.selected_range_out + 1):
if track.clips[i].is_blanck_clip == True:
blanks_stripped_start = i + 1
else:
break
self.selected_range_in = blanks_stripped_start
if self.selected_range_in > self.selected_range_out:
return # the 1 cli in selection range is blank
# Drop blanks from end
blanks_stripped_end = self.selected_range_out
for i in range(self.selected_range_out, self.selected_range_in - 1, - 1):
if track.clips[i].is_blanck_clip == True:
blanks_stripped_end = i - 1
else:
break
self.selected_range_out = blanks_stripped_end
# Get clip lengths
for i in range(self.selected_range_in, self.selected_range_out + 1):
clip = track.clips[i]
self.clip_lengths.append(clip.clip_out - clip.clip_in + 1)
self.clip_is_media.append(clip.is_blanck_clip == False)
# Get bounding frames
self.range_frame_in = track.clip_start(self.selected_range_in)
self.range_frame_out = track.clip_start(self.selected_range_out) + self.clip_lengths[-1]
def is_empty(self):
if len(self.clip_lengths) == 0:
return True
return False
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/cairoarea.py 0000664 0000000 0000000 00000011620 13610327166 0025403 0 ustar 00root root 0000000 0000000 """
Flowblade Movie Editor is a nonlinear video editor.
Copyright 2012 Janne Liljeblad.
This file is part of Flowblade Movie Editor .
Flowblade Movie Editor 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.
Flowblade Movie Editor is distributed in the hope that 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 Flowblade Movie Editor. If not, see .
"""
"""
Module contains CairoDrawableArea2 widget. You can draw onto it using
Cairo by setting the draw function on creation, and listen to its mouse and keyboard events.
"""
from gi.repository import Gtk
from gi.repository import Gdk
bg_color = None
class CairoDrawableArea2(Gtk.DrawingArea):
def __init__(self, pref_width, pref_height, func_draw, use_widget_bg=False):
Gtk.DrawingArea.__init__(self)
self.add_events(Gdk.EventMask.BUTTON_PRESS_MASK)
self.add_events(Gdk.EventMask.BUTTON_RELEASE_MASK)
self.add_events(Gdk.EventMask.BUTTON_MOTION_MASK)
self.add_events(Gdk.EventMask.SCROLL_MASK)
self.add_events(Gdk.EventMask.ENTER_NOTIFY_MASK)
self.add_events(Gdk.EventMask.LEAVE_NOTIFY_MASK)
self.add_events(Gdk.EventMask.KEY_PRESS_MASK)
self.add_events(Gdk.EventMask.POINTER_MOTION_HINT_MASK)
self.set_size_request(pref_width, pref_height)
self._use_widget_bg = use_widget_bg
# Connect signal listeners
self._draw_func = func_draw
self.connect('draw', self._draw_event)
self.connect('button-press-event', self._button_press_event)
self.connect('button-release-event', self._button_release_event)
self.connect('motion-notify-event', self._motion_notify_event)
self.connect('enter-notify-event', self._enter_notify_event)
self.connect('leave-notify-event', self._leave_notify_event)
self.connect("scroll-event", self._mouse_scroll_event)
# Signal handler funcs. These are monkeypatched as needed on codes sites
# that create the objects.
self.press_func = self._press
self.release_func = self._release
self.motion_notify_func = self._motion_notify
self.leave_notify_func = self._leave
self.enter_notify_func = self._enter
self.mouse_scroll_func = None
self.set_property("can-focus", True)
self.grab_focus_on_press = True
def add_pointer_motion_mask(self):
self.add_events(Gdk.EventMask.POINTER_MOTION_MASK)
def set_pref_size(self, pref_width, pref_height):
self.set_size_request(pref_width, pref_height)
def _draw_event(self, widget, cr):
a = self.get_allocation()
self._draw_func(None, cr, (a.x, a.y, a.width, a.height)) # 'None' is event object that was used to pass through here.
# GTK2 used a tuple for allocation and all draw funcs expect it, so we provide
# allocation as tuple.
return False
# ------------------------------------------------------------ Signal listeners
# These pass on events to handler functions that
# are by default the noop functions here, but are monkeypathed
# at creation sites as needed.
def _button_press_event(self, widget, event):
if self.grab_focus_on_press:
self.grab_focus()
self.press_func(event)
return False
def _button_release_event(self, widget, event):
self.release_func(event)
return False
def _motion_notify_event(self, widget, event):
if event.is_hint:
winbdow, x, y, state = event.window.get_pointer()
else:
x = event.x
y = event.y
state = event.get_state()
self.motion_notify_func(x, y, state)
def _enter_notify_event(self, widget, event):
self.enter_notify_func(event)
def _leave_notify_event(self, widget, event):
self.leave_notify_func(event)
def _mouse_scroll_event(self, widget, event):
if self.mouse_scroll_func == None:
return
self.mouse_scroll_func(event)
# ------------------------------------------------------- Noop funcs for unhandled events
def _press(self, event):
pass
def _release(self, event):
pass
def _motion_notify(self, x, y, state):
pass
def _enter(self, event):
pass
def _leave(self, event):
pass
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/clipeffectseditor.py 0000664 0000000 0000000 00000070551 13610327166 0027163 0 ustar 00root root 0000000 0000000 """
Flowblade Movie Editor is a nonlinear video editor.
Copyright 2012 Janne Liljeblad.
This file is part of Flowblade Movie Editor .
Flowblade Movie Editor 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.
Flowblade Movie Editor is distributed in the hope that 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 Flowblade Movie Editor. If not, see .
"""
"""
Module handles clip effects editing logic and gui
"""
import copy
from gi.repository import GLib
from gi.repository import Gtk
import pickle
import time
import atomicfile
import dialogs
import dialogutils
import edit
import editorpersistance
import editorstate
from editorstate import PROJECT
import gui
import guicomponents
import guiutils
import mltfilters
import propertyedit
import propertyeditorbuilder
import respaths
import translations
import updater
import utils
widgets = utils.EmptyClass()
clip = None # Clip being edited
track = None # Track of the clip being editeds
clip_index = None # Index of clip being edited
block_changed_update = False # Used to block unwanted callback update from "changed", hack and a broken one, look to fix
current_filter_index = -1 # Needed to find right filter object when saving/loading effect values
# This is updated when filter panel is displayed and cleared when removed.
# Used to update kfeditors with external tline frame position changes
keyframe_editor_widgets = []
# Filter stack DND requires some state info to be maintained to make sure that it's only done when certain events
# happen in a certain sequence.
NOT_ON = 0
MOUSE_PRESS_DONE = 1
INSERT_DONE = 2
stack_dnd_state = NOT_ON
stack_dnd_event_time = 0.0
stack_dnd_event_info = None
filters_notebook_index = 2
def get_clip_effects_editor_panel(group_combo_box, effects_list_view):
create_widgets()
stack_label = guiutils.bold_label(_("Clip Filters Stack"))
label_row = guiutils.get_left_justified_box([stack_label])
guiutils.set_margins(label_row, 0, 4, 0, 0)
effect_stack = widgets.effect_stack_view
for group in mltfilters.groups:
group_name, filters_array = group
group_combo_box.append_text(group_name)
group_combo_box.set_active(0)
# Same callback function works for filter select window too
group_combo_box.connect("changed",
lambda w,e: _group_selection_changed(w,effects_list_view),
None)
widgets.group_combo = group_combo_box
widgets.effect_list_view = effects_list_view
set_enabled(False)
exit_button_vbox = Gtk.VBox(False, 2)
exit_button_vbox.pack_start(widgets.exit_button, False, False, 0)
info_row = Gtk.HBox(False, 2)
info_row.pack_start(widgets.hamburger_launcher.widget, False, False, 0)
info_row.pack_start(Gtk.Label(), True, True, 0)
info_row.pack_start(widgets.clip_info, False, False, 0)
info_row.pack_start(Gtk.Label(), True, True, 0)
combo_row = Gtk.HBox(False, 2)
combo_row.pack_start(group_combo_box, True, True, 0)
group_name, filters_array = mltfilters.groups[0]
effects_list_view.fill_data_model(filters_array)
effects_list_view.treeview.get_selection().select_path("0")
effects_vbox = Gtk.VBox(False, 2)
if editorstate.SCREEN_HEIGHT < 1023:
stack_buttons_box = Gtk.HBox(False,1)
stack_buttons_box.pack_start(widgets.del_effect_b, True, True, 0)
stack_buttons_box.pack_start(widgets.toggle_all, False, False, 0)
stack_buttons_box.pack_start(guiutils.pad_label(74, 10), False, False, 0)
guiutils.set_margins(stack_buttons_box, 4, 4, 0, 0)
stack_vbox = Gtk.VBox(False, 2)
stack_vbox.pack_start(stack_buttons_box, False, False, 0)
stack_vbox.pack_start(effect_stack, True, True, 0)
add_buttons_box = Gtk.HBox(True,1)
add_buttons_box.pack_start(widgets.add_effect_b, True, True, 0)
add_buttons_box.pack_start(Gtk.Label(), True, True, 0)
guiutils.set_margins(add_buttons_box, 4, 4, 0, 0)
groups_vbox = Gtk.VBox(False, 2)
groups_vbox.pack_start(add_buttons_box, False, False, 0)
groups_vbox.pack_start(combo_row, False, False, 0)
groups_vbox.pack_start(effects_list_view, True, True, 0)
notebook = Gtk.Notebook()
notebook.append_page(stack_vbox, Gtk.Label(label=_("Stack")))
notebook.append_page(groups_vbox, Gtk.Label(label=_("Filters")))
effects_vbox.pack_start(notebook, True, True, 0)
else:
ad_buttons_box = Gtk.HBox(True,1)
ad_buttons_box.pack_start(widgets.add_effect_b, True, True, 0)
ad_buttons_box.pack_start(widgets.del_effect_b, True, True, 0)
stack_buttons_box = Gtk.HBox(False,1)
stack_buttons_box.pack_start(ad_buttons_box, True, True, 0)
stack_buttons_box.pack_start(widgets.toggle_all, False, False, 0)
effects_vbox.pack_start(label_row, False, False, 0)
effects_vbox.pack_start(stack_buttons_box, False, False, 0)
effects_vbox.pack_start(effect_stack, True, True, 0)
effects_vbox.pack_start(combo_row, False, False, 0)
effects_vbox.pack_start(effects_list_view, True, True, 0)
widgets.group_combo.set_tooltip_text(_("Select Filter Group"))
widgets.effect_list_view.set_tooltip_text(_("Current group Filters"))
return effects_vbox, info_row
def _group_selection_changed(group_combo, filters_list_view):
group_name, filters_array = mltfilters.groups[group_combo.get_active()]
filters_list_view.fill_data_model(filters_array)
filters_list_view.treeview.get_selection().select_path("0")
def set_clip(new_clip, new_track, new_index, show_tab=True):
"""
Sets clip being edited and inits gui.
"""
global clip, track, clip_index
if clip == new_clip and track == new_track and clip_index == new_index and show_tab==False:
return
clip = new_clip
track = new_track
clip_index = new_index
widgets.clip_info.display_clip_info(clip, track, clip_index)
set_enabled(True)
update_stack_view()
if len(clip.filters) > 0:
path = str(len(clip.filters) - 1)
# Causes edit_effect_selected() called as it is the "change" listener
widgets.effect_stack_view.treeview.get_selection().select_path(path)
else:
effect_selection_changed()
if show_tab:
gui.middle_notebook.set_current_page(filters_notebook_index) # 2 == index of clipeditor page in notebook
def effect_select_row_double_clicked(treeview, tree_path, col):
add_currently_selected_effect()
def filter_stack_button_press(treeview, event):
path_pos_tuple = treeview.get_path_at_pos(int(event.x), int(event.y))
if path_pos_tuple == None:
row = -1 # Empty row was clicked
else:
path, column, x, y = path_pos_tuple
selection = treeview.get_selection()
selection.unselect_all()
selection.select_path(path)
(model, rows) = selection.get_selected_rows()
row = max(rows[0])
if row == -1:
return False
if event.button == 3:
guicomponents.display_filter_stack_popup_menu(row, treeview, _filter_stack_menu_item_selected, event)
return True
return False
def _filter_stack_menu_item_selected(widget, data):
item_id, row, treeview = data
if item_id == "toggle":
toggle_filter_active(row)
if item_id == "reset":
reset_filter_values()
if item_id == "movedown":
delete_row = row
insert_row = row + 2
if insert_row > len(clip.filters):
insert_row = len(clip.filters)
do_stack_move(insert_row, delete_row)
if item_id == "moveup":
delete_row = row + 1
insert_row = row - 1
if insert_row < 0:
insert_row = 0
do_stack_move(insert_row, delete_row)
def _quit_editing_clip_clicked(): # this is a button callback
clear_clip()
def clear_clip():
"""
Removes clip from effects editing gui.
"""
global clip
clip = None
_set_no_clip_info()
effect_selection_changed()
update_stack_view()
set_enabled(False)
def _set_no_clip_info():
widgets.clip_info.set_no_clip_info()
def create_widgets():
"""
Widgets for editing clip effects properties.
"""
# Aug-2019 - SvdB - BB
prefs = editorpersistance.prefs
widgets.clip_info = guicomponents.ClipInfoPanel()
widgets.exit_button = Gtk.Button()
icon = Gtk.Image.new_from_stock(Gtk.STOCK_CLOSE, Gtk.IconSize.MENU)
widgets.exit_button.set_image(icon)
widgets.exit_button.connect("clicked", lambda w: _quit_editing_clip_clicked())
widgets.exit_button.set_tooltip_text(_("Quit editing Clip in editor"))
widgets.effect_stack_view = guicomponents.FilterSwitchListView(lambda ts: effect_selection_changed(),
toggle_filter_active, dnd_row_deleted, dnd_row_inserted)
widgets.effect_stack_view.treeview.connect("button-press-event", lambda w,e, wtf: stack_view_pressed(), None)
gui.effect_stack_list_view = widgets.effect_stack_view
widgets.value_edit_box = Gtk.VBox()
widgets.value_edit_frame = Gtk.Frame()
widgets.value_edit_frame.set_shadow_type(Gtk.ShadowType.NONE)
widgets.value_edit_frame.add(widgets.value_edit_box)
widgets.add_effect_b = Gtk.Button()
widgets.add_effect_b.set_image(guiutils.get_image("filter_add"))
widgets.del_effect_b = Gtk.Button()
widgets.del_effect_b.set_image(guiutils.get_image("filter_delete"))
widgets.toggle_all = Gtk.Button()
widgets.toggle_all.set_image(guiutils.get_image("filters_all_toggle"))
widgets.add_effect_b.connect("clicked", lambda w,e: add_effect_pressed(), None)
widgets.del_effect_b.connect("clicked", lambda w,e: delete_effect_pressed(), None)
widgets.toggle_all.connect("clicked", lambda w: toggle_all_pressed())
widgets.hamburger_launcher = guicomponents.HamburgerPressLaunch(_hamburger_launch_pressed)
guiutils.set_margins(widgets.hamburger_launcher.widget, 6, 8, 1, 0)
# These are created elsewhere and then monkeypatched here
widgets.group_combo = None
widgets.effect_list_view = None
widgets.clip_info.set_tooltip_text(_("Clip being edited"))
widgets.effect_stack_view.set_tooltip_text(_("Clip Filter Stack"))
widgets.add_effect_b.set_tooltip_text(_("Add Filter to Clip Filter Stack"))
widgets.del_effect_b.set_tooltip_text(_("Delete Filter from Clip Filter Stack"))
widgets.toggle_all.set_tooltip_text(_("Toggle all Filters On/Off"))
def set_enabled(value):
widgets.clip_info.set_enabled( value)
widgets.add_effect_b.set_sensitive(value)
widgets.del_effect_b.set_sensitive(value)
widgets.effect_stack_view.treeview.set_sensitive(value)
widgets.exit_button.set_sensitive(value)
widgets.toggle_all.set_sensitive(value)
widgets.hamburger_launcher.set_sensitive(value)
widgets.hamburger_launcher.widget.queue_draw()
def update_stack_view():
if clip != None:
filter_infos = []
for f in clip.filters:
filter_infos.append(f.info)
widgets.effect_stack_view.fill_data_model(filter_infos, clip.filters)
else:
widgets.effect_stack_view.fill_data_model([], [])
widgets.effect_stack_view.treeview.queue_draw()
def update_stack_view_changed_blocked():
global block_changed_update
block_changed_update = True
update_stack_view()
block_changed_update = False
def add_currently_selected_effect():
# Check we have clip
if clip == None:
return
filter_info = get_selected_filter_info()
action = get_filter_add_action(filter_info, clip)
action.do_edit() # gui update in callback from EditAction object.
updater.repaint_tline()
def get_filter_add_action(filter_info, target_clip):
if filter_info.multipart_filter == False:
# Maybe show info on using alpha filters
if filter_info.group == "Alpha":
GLib.idle_add(_alpha_filter_add_maybe_info, filter_info)
data = {"clip":target_clip,
"filter_info":filter_info,
"filter_edit_done_func":filter_edit_done}
action = edit.add_filter_action(data)
else:
data = {"clip":target_clip,
"filter_info":filter_info,
"filter_edit_done_func":filter_edit_done}
action = edit.add_multipart_filter_action(data)
return action
def _alpha_filter_add_maybe_info(filter_info):
if editorpersistance.prefs.show_alpha_info_message == True:
dialogs.alpha_info_msg(_alpha_info_dialog_cb, translations.get_filter_name(filter_info.name))
def _alpha_info_dialog_cb(dialog, response_id, dont_show_check):
if dont_show_check.get_active() == True:
editorpersistance.prefs.show_alpha_info_message = False
editorpersistance.save()
dialog.destroy()
def get_selected_filter_info():
# Get current selection on effects treeview - that's a vertical list.
treeselection = gui.effect_select_list_view.treeview.get_selection()
(model, rows) = treeselection.get_selected_rows()
row = rows[0]
row_index = max(row)
# Add filter
group_name, filters_array = mltfilters.groups[gui.effect_select_combo_box.get_active()]
return filters_array[row_index]
def add_effect_pressed():
add_currently_selected_effect()
def delete_effect_pressed():
if len(clip.filters) == 0:
return
# Block updates until we have set selected row
global edit_effect_update_blocked
edit_effect_update_blocked = True
data = {"clip":clip,
"index":current_filter_index,
"filter_edit_done_func":filter_edit_done}
action = edit.remove_filter_action(data)
action.do_edit()
updater.repaint_tline()
# Set last filter selected and display in editor
edit_effect_update_blocked = False
if len(clip.filters) == 0:
effect_selection_changed() # to display info text
return
path = str(len(clip.filters) - 1)
# Causes edit_effect_selected() called as it is the "change" listener
widgets.effect_stack_view.treeview.get_selection().select_path(path)
def toggle_all_pressed():
for i in range(0, len(clip.filters)):
filter_object = clip.filters[i]
filter_object.active = (filter_object.active == False)
filter_object.update_mlt_disabled_value()
update_stack_view()
def reset_filter_values():
treeselection = widgets.effect_stack_view.treeview.get_selection()
(model, rows) = treeselection.get_selected_rows()
row = rows[0]
row_index = max(row)
clip.filters[row_index].reset_values(PROJECT().profile, clip)
effect_selection_changed()
def toggle_filter_active(row, update_stack_view=True):
filter_object = clip.filters[row]
filter_object.active = (filter_object.active == False)
filter_object.update_mlt_disabled_value()
if update_stack_view == True:
update_stack_view_changed_blocked()
def dnd_row_deleted(model, path):
now = time.time()
global stack_dnd_state, stack_dnd_event_time, stack_dnd_event_info
if stack_dnd_state == INSERT_DONE:
if (now - stack_dnd_event_time) < 0.1:
stack_dnd_state = NOT_ON
insert_row = int(stack_dnd_event_info)
delete_row = int(path.to_string())
stack_dnd_event_info = (insert_row, delete_row)
# Because of dnd is gtk thing for some internal reason it needs to complete before we go on
# touching storemodel again with .clear() or it dies in gtktreeviewaccessible.c
GLib.idle_add(do_dnd_stack_move)
else:
stack_dnd_state = NOT_ON
else:
stack_dnd_state = NOT_ON
def dnd_row_inserted(model, path, tree_iter):
global stack_dnd_state, stack_dnd_event_time, stack_dnd_event_info
if stack_dnd_state == MOUSE_PRESS_DONE:
stack_dnd_state = INSERT_DONE
stack_dnd_event_time = time.time()
stack_dnd_event_info = path.to_string()
else:
stack_dnd_state = NOT_ON
def do_dnd_stack_move():
insert, delete_row = stack_dnd_event_info
do_stack_move(insert, delete_row)
def do_stack_move(insert_row, delete_row):
if abs(insert_row - delete_row) < 2: # filter was dropped on its previous place or cannot moved further up or down
return
# The insert insert_row and delete_row values are rows we get when listening
# "row-deleted" and "row-inserted" events after setting treeview "reorderable"
# Dnd is detected by order and timing of these events together with mouse press event
data = {"clip":clip,
"insert_index":insert_row,
"delete_index":delete_row,
"filter_edit_done_func":filter_edit_done}
action = edit.move_filter_action(data)
action.do_edit()
def stack_view_pressed():
global stack_dnd_state
stack_dnd_state = MOUSE_PRESS_DONE
def reinit_current_effect():
effect_selection_changed(True)
def effect_selection_changed(use_current_filter_index=False):
global keyframe_editor_widgets, current_filter_index
# Check we have clip
if clip == None:
keyframe_editor_widgets = []
show_text_in_edit_area(_("No Clip"))
return
# Check we actually have filters so we can display one.
# If not, clear previous filters from view.
if len(clip.filters) == 0:
show_text_in_edit_area(_("Clip Has No Filters"))
keyframe_editor_widgets = []
return
# "changed" get's called twice when adding filter and selecting last
# so we use this do this only once
if block_changed_update == True:
return
keyframe_editor_widgets = []
# Get selected row which is also index of filter in clip.filters
treeselection = widgets.effect_stack_view.treeview.get_selection()
(model, rows) = treeselection.get_selected_rows()
# If we don't get legal selection select first filter
try:
row = rows[0]
filter_index = max(row)
except:
filter_index = 0
# use_current_filter_index == False is used when user changes edited filter or clip.
if use_current_filter_index == True:
filter_index = current_filter_index
filter_object = clip.filters[filter_index]
current_filter_index = filter_index
# Create EditableProperty wrappers for properties
editable_properties = propertyedit.get_filter_editable_properties(
clip,
filter_object,
filter_index,
track,
clip_index)
# Get editors and set them displayed
vbox = Gtk.VBox(False, 0)
try:
filter_name = translations.filter_names[filter_object.info.name]
except KeyError:
filter_name = filter_object.info.name
filter_name_label = Gtk.Label(label= "" + filter_name + "")
filter_name_label.set_use_markup(True)
vbox.pack_start(filter_name_label, False, False, 0)
vbox.pack_start(guicomponents.EditorSeparator().widget, False, False, 0)
if len(editable_properties) > 0:
# Create editor row for each editable property
for ep in editable_properties:
editor_row = propertyeditorbuilder.get_editor_row(ep)
if editor_row == None:
continue
# Set keyframe editor widget to be updated for frame changes if such is created
try:
editor_type = ep.args[propertyeditorbuilder.EDITOR]
except KeyError:
editor_type = propertyeditorbuilder.SLIDER # this is the default value
if ((editor_type == propertyeditorbuilder.KEYFRAME_EDITOR)
or (editor_type == propertyeditorbuilder.KEYFRAME_EDITOR_RELEASE)
or (editor_type == propertyeditorbuilder.KEYFRAME_EDITOR_CLIP)
or (editor_type == propertyeditorbuilder.FILTER_RECT_GEOM_EDITOR)):
keyframe_editor_widgets.append(editor_row)
# if slider property is being dedited as keyrame property
if hasattr(editor_row, "is_kf_editor"):
keyframe_editor_widgets.append(editor_row)
vbox.pack_start(editor_row, False, False, 0)
if not hasattr(editor_row, "no_separator"):
vbox.pack_start(guicomponents.EditorSeparator().widget, False, False, 0)
# Create NonMltEditableProperty wrappers for properties
non_mlteditable_properties = propertyedit.get_non_mlt_editable_properties( clip,
filter_object,
filter_index)
# Extra editors. Editable properties may have already been created
# with "editor=no_editor" and now extra editors may be created to edit those
# Non mlt properties are added as these are only need with extraeditors
editable_properties.extend(non_mlteditable_properties)
editor_rows = propertyeditorbuilder.get_filter_extra_editor_rows(filter_object, editable_properties)
for editor_row in editor_rows:
vbox.pack_start(editor_row, False, False, 0)
if not hasattr(editor_row, "no_separator"):
vbox.pack_start(guicomponents.EditorSeparator().widget, False, False, 0)
vbox.pack_start(guiutils.pad_label(12,12), False, False, 0)
vbox.pack_start(Gtk.Label(), True, True, 0)
else:
vbox.pack_start(Gtk.Label(label=_("No editable parameters")), True, True, 0)
vbox.show_all()
scroll_window = Gtk.ScrolledWindow()
scroll_window.add_with_viewport(vbox)
scroll_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
scroll_window.show_all()
widgets.value_edit_frame.remove(widgets.value_edit_box)
widgets.value_edit_frame.add(scroll_window)
widgets.value_edit_box = scroll_window
def show_text_in_edit_area(text):
vbox = Gtk.VBox(False, 0)
filler = Gtk.EventBox()
filler.add(Gtk.Label())
vbox.pack_start(filler, True, True, 0)
info = Gtk.Label(label=text)
info.set_sensitive(False)
filler = Gtk.EventBox()
filler.add(info)
vbox.pack_start(filler, False, False, 0)
filler = Gtk.EventBox()
filler.add(Gtk.Label())
vbox.pack_start(filler, True, True, 0)
vbox.show_all()
scroll_window = Gtk.ScrolledWindow()
scroll_window.add_with_viewport(vbox)
scroll_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
scroll_window.show_all()
widgets.value_edit_frame.remove(widgets.value_edit_box)
widgets.value_edit_frame.add(scroll_window)
widgets.value_edit_box = scroll_window
def clear_effects_edit_panel():
widgets.value_edit_frame.remove(widgets.value_edit_box)
label = Gtk.Label()
widgets.value_edit_frame.add(label)
widgets.value_edit_box = label
def filter_edit_done(edited_clip, index=-1):
"""
EditAction object calls this after edits and undos and redos.
"""
if edited_clip != clip: # This gets called by all undos/redos, we only want to update if clip being edited here is affected
return
global block_changed_update
block_changed_update = True
update_stack_view()
block_changed_update = False
# Select row in effect stack view and so display corresponding effect editor panel.
if not(index < 0):
widgets.effect_stack_view.treeview.get_selection().select_path(str(index))
else: # no effects after edit, clear effect editor panel
clear_effects_edit_panel()
def display_kfeditors_tline_frame(frame):
for kf_widget in keyframe_editor_widgets:
kf_widget.display_tline_frame(frame)
def update_kfeditors_sliders(frame):
for kf_widget in keyframe_editor_widgets:
kf_widget.update_slider_value_display(frame)
def update_kfeditors_positions():
if clip == None:
return
for kf_widget in keyframe_editor_widgets:
kf_widget.update_clip_pos()
# ------------------------------------------------ SAVE; LOAD etc. from hamburger menu
def _hamburger_launch_pressed(widget, event):
guicomponents.get_clip_effects_editor_hamburger_menu(event, _clip_hamburger_item_activated)
def _clip_hamburger_item_activated(widget, msg):
if msg == "save":
filter_object = clip.filters[current_filter_index]
default_name = filter_object.info.name + _("_effect_values") + ".data"
dialogs.save_effects_compositors_values(_save_effect_values_dialog_callback, default_name)
elif msg == "load":
dialogs.load_effects_compositors_values_dialog(_load_effect_values_dialog_callback)
elif msg == "reset":
_reset_filter_values()
elif msg == "delete":
_delete_effect()
elif msg == "close":
clear_clip()
def _save_effect_values_dialog_callback(dialog, response_id):
if response_id == Gtk.ResponseType.ACCEPT:
save_path = dialog.get_filenames()[0]
filter_object = clip.filters[current_filter_index]
effect_data = EffectValuesSaveData(filter_object)
effect_data.save(save_path)
dialog.destroy()
def _load_effect_values_dialog_callback(dialog, response_id):
if response_id == Gtk.ResponseType.ACCEPT:
load_path = dialog.get_filenames()[0]
effect_data = utils.unpickle(load_path)
filter_object = clip.filters[current_filter_index]
if effect_data.data_applicable(filter_object.info):
effect_data.set_effect_values(filter_object)
effect_selection_changed()
else:
# Info window
saved_effect_name = effect_data.info.name
current_effect_name = filter_object.info.name
primary_txt = _("Saved Filter data not applicaple for this Filter!")
secondary_txt = _("Saved data is for ") + saved_effect_name + " Filter,\n" + _("current edited Filter is ") + current_effect_name + "."
dialogutils.warning_message(primary_txt, secondary_txt, gui.editor_window.window)
dialog.destroy()
def _reset_filter_values():
filter_object = clip.filters[current_filter_index]
info = filter_object.info
if filter_object.info.multipart_filter == True:
filter_object.value = info.multipart_value
filter_object.properties = copy.deepcopy(info.properties)
filter_object.non_mlt_properties = copy.deepcopy(info.non_mlt_properties)
effect_selection_changed()
def _delete_effect():
delete_effect_pressed()
class EffectValuesSaveData:
def __init__(self, filter_object):
self.info = filter_object.info
self.multipart_filter = self.info.multipart_filter
# Values of these are edited by the user.
self.properties = copy.deepcopy(filter_object.properties)
try:
self.non_mlt_properties = copy.deepcopy(filter_object.non_mlt_properties)
except:
self.non_mlt_properties = [] # Versions prior 0.14 do not have non_mlt_properties and fail here on load
if self.multipart_filter == True:
self.value = filter_object.value
else:
self.value = None
def save(self, save_path):
with atomicfile.AtomicFileWriter(save_path, "wb") as afw:
write_file = afw.get_file()
pickle.dump(self, write_file)
def data_applicable(self, filter_info):
if isinstance(self.info, filter_info.__class__):
return self.info.__dict__ == filter_info.__dict__
return False
def set_effect_values(self, filter_object):
if self.multipart_filter == True:
filter_object.value = self.value
filter_object.properties = copy.deepcopy(self.properties)
filter_object.non_mlt_properties = copy.deepcopy(self.non_mlt_properties)
filter_object.update_mlt_filter_properties_all()
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/clipenddragmode.py 0000664 0000000 0000000 00000016166 13610327166 0026610 0 ustar 00root root 0000000 0000000 """
Flowblade Movie Editor is a nonlinear video editor.
Copyright 2012 Janne Liljeblad.
This file is part of Flowblade Movie Editor .
Flowblade Movie Editor 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.
Flowblade Movie Editor is distributed in the hope that 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 Flowblade Movie Editor. If not, see .
"""
"""
Module handles clip end dragging edits.
"""
import appconsts
import gui
import edit
from editorstate import current_sequence
import editorstate
import tlinewidgets
import updater
# Edit mode that was active when mode was entered
_enter_mode = None
_enter_draw_func = None
def maybe_init_for_mouse_press(event, frame):
# See if we actually hit a clip
track = tlinewidgets.get_track(event.y)
if track == None:
return
if track.id < 1 or (track.id >= len(current_sequence().tracks) - 1):
return False
clip_index = current_sequence().get_clip_index(track, frame)
if clip_index == -1:
return
clip = track.clips[clip_index]
if clip.is_blanck_clip:
return
# Now we will in fact enter CLIP_END_DRAG edit mode
# See if we're dragging clip end or start
cut_frame = current_sequence().get_closest_cut_frame(track.id, frame)
editing_clip_end = True
if frame >= cut_frame:
editing_clip_end = False
else:
cut_frame = cut_frame - (clip.clip_out - clip.clip_in)
if editing_clip_end == True: # clip end drags
bound_end = cut_frame - clip.clip_in + clip.get_length() - 1 # get_length() is available media length, not current clip length
bound_start = cut_frame - 1
if clip_index == len(track.clips) - 1: # last clip
bound_end = bound_end - 1
else: # clip beginning drags
bound_start = cut_frame - clip.clip_in
bound_end = cut_frame + (clip.clip_out - clip.clip_in) + 1
global _enter_mode, _enter_draw_func, _edit_data
_enter_mode = editorstate.edit_mode
editorstate.edit_mode = editorstate.CLIP_END_DRAG
_enter_draw_func = tlinewidgets.canvas_widget.edit_mode_overlay_draw_func
_edit_data = {}
_edit_data["track"] = track
_edit_data["clip_index"] = clip_index
_edit_data["frame"] = frame
_edit_data["press_frame"] = frame
_edit_data["editing_clip_end"] = editing_clip_end
_edit_data["bound_end"] = bound_end
_edit_data["bound_start"] = bound_start
_edit_data["track_height"] = track.height
_edit_data["orig_in"] = cut_frame - 1
_edit_data["orig_out"] = cut_frame + (clip.clip_out - clip.clip_in)
tlinewidgets.set_edit_mode(_edit_data, tlinewidgets.draw_clip_end_drag_overlay)
if tlinewidgets.pointer_context == appconsts.POINTER_CONTEXT_NONE:
# We did CTRL + Mouse Right to get here, we need to set pointer context to left or right
if editing_clip_end == True:
tlinewidgets.pointer_context = appconsts.POINTER_CONTEXT_END_DRAG_RIGHT
else:
tlinewidgets.pointer_context = appconsts.POINTER_CONTEXT_END_DRAG_LEFT
gui.editor_window.set_cursor_to_mode()
def mouse_press(event, frame):
frame = _legalize_frame(frame)
_edit_data["frame"] = frame
updater.repaint_tline()
def mouse_move(x, y, frame, state):
frame = _legalize_frame(frame)
_edit_data["frame"] = frame
updater.repaint_tline()
def mouse_release(x, y, frame, state):
frame = _legalize_frame(frame)
_edit_data["frame"] = frame
updater.repaint_tline()
track = _edit_data["track"]
clip_index = _edit_data["clip_index"]
clip = track.clips[clip_index]
orig_in = _edit_data["orig_in"]
orig_out = _edit_data["orig_out"]
# do edit
# Dragging clip end
if _edit_data["editing_clip_end"] == True:
delta = frame - orig_out
# next clip is not blank or last clip
if ((clip_index == len(track.clips) - 1) or
(track.clips[clip_index + 1].is_blanck_clip == False)):
data = {"track":track,
"index":clip_index,
"clip":clip,
"delta":delta}
action = edit.trim_last_clip_end_action(data)
action.do_edit()
else: # next clip is blank
blank_clip = track.clips[clip_index + 1]
blank_clip_length = blank_clip.clip_length()
data = {"track":track,
"index":clip_index,
"clip":clip,
"blank_clip_length":blank_clip_length,
"delta":delta}
if delta < blank_clip_length: # partial blank overwrite
action = edit.clip_end_drag_on_blank_action(data)
action.do_edit()
else: # full blank replace
action = edit.clip_end_drag_replace_blank_action(data)
action.do_edit()
else:# Dragging clip start
delta = frame - orig_in - 1 # -1 because..uhh..inclusive exclusive something something
# prev clip is not blank or first clip
if ((clip_index == 0) or
(track.clips[clip_index - 1].is_blanck_clip == False)):
data = {"track":track,
"index":clip_index,
"clip":clip,
"delta":delta}
action = edit.trim_start_action(data)
action.do_edit()
else: # prev clip is blank
blank_clip = track.clips[clip_index - 1]
blank_clip_length = blank_clip.clip_length()
data = {"track":track,
"index":clip_index,
"clip":clip,
"blank_clip_length":blank_clip_length,
"delta":delta}
if -delta < blank_clip_length: # partial blank overwrite
action = edit.clip_start_drag_on_blank_action(data)
action.do_edit()
else: # full blank replace
action = edit.clip_start_drag_replace_blank_action(data)
action.do_edit()
_exit_clip_end_drag()
updater.repaint_tline()
def _exit_clip_end_drag():
# Go back to enter mode
editorstate.edit_mode = _enter_mode
tlinewidgets.set_edit_mode(None, _enter_draw_func)
tlinewidgets.pointer_context = appconsts.POINTER_CONTEXT_NONE
gui.editor_window.set_cursor_to_mode()
updater.repaint_tline()
def _legalize_frame(frame):
start = _edit_data["bound_start"]
end = _edit_data["bound_end"]
if _edit_data["editing_clip_end"] == True:
if frame > end:
frame = end
if frame < (start + 1):
frame = start + 1
else:
if frame > end - 1:
frame = end - 1
if frame < start:
frame = start
return frame
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/clipmenuaction.py 0000664 0000000 0000000 00000057665 13610327166 0026512 0 ustar 00root root 0000000 0000000 """
Flowblade Movie Editor is a nonlinear video editor.
Copyright 2014 Janne Liljeblad.
This file is part of Flowblade Movie Editor .
Flowblade Movie Editor 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.
Flowblade Movie Editor is distributed in the hope that 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 Flowblade Movie Editor. If not, see .
"""
"""
This module handles actions initiated from clip and compositor popup menus.
"""
from PIL import Image
from gi.repository import GLib
from gi.repository import Gtk
import mlt
from operator import itemgetter
import os
import shutil
import time
import audiowaveform
import audiosync
import appconsts
import clipeffectseditor
import compositeeditor
import dialogs
import dialogutils
import gui
import guicomponents
import edit
import editevent
from editorstate import current_sequence
from editorstate import get_track
from editorstate import PROJECT
from editorstate import PLAYER
import kftoolmode
import mlttransitions
import modesetting
import movemodes
import syncsplitevent
import tlinewidgets
import tlineaction
import updater
import userfolders
import utils
_match_frame_writer = None
# ---------------------------------- clip menu
def display_clip_menu(y, event, frame):
# See if we actually hit a clip
track = tlinewidgets.get_track(y)
if track == None:
return False
clip_index = current_sequence().get_clip_index(track, frame)
if clip_index == -1:
return False
# Can't do anything to clips in locked tracks
if dialogutils.track_lock_check_and_user_info(track):
return False
# Display popup
gui.tline_canvas.drag_on = False
pressed_clip = track.clips[clip_index]
if pressed_clip.is_blanck_clip == False:
movemodes.select_clip(track.id, clip_index)
else:
movemodes.select_blank_range(track, pressed_clip)
if track.type == appconsts.VIDEO:
guicomponents.display_clip_popup_menu(event, pressed_clip, \
track, _clip_menu_item_activated)
elif track.type == appconsts.AUDIO:
guicomponents.display_audio_clip_popup_menu(event, pressed_clip, \
track, _clip_menu_item_activated)
return True
def _clip_menu_item_activated(widget, data):
# Callback from selected clipmenu item
clip, track, item_id, item_data = data
handler = POPUP_HANDLERS[item_id]
handler(data)
def _compositor_menu_item_activated(widget, data):
action_id, compositor = data
if action_id == "open in editor":
compositeeditor.set_compositor(compositor)
elif action_id == "delete":
compositor.selected = False
data = {"compositor":compositor}
action = edit.delete_compositor_action(data)
action.do_edit()
elif action_id == "sync with origin":
tlineaction.sync_compositor(compositor)
elif action_id == "set auto follow":
compositor.obey_autofollow = widget.get_active()
updater.repaint_tline()
def _delete_compositors(data):
clip, track, item_id, x = data
compositors = current_sequence().get_clip_compositors(clip)
for compositor in compositors:
data = {"compositor":compositor}
action = edit.delete_compositor_action(data)
action.do_edit()
def _open_clip_in_effects_editor(data):
updater.open_clip_in_effects_editor(data)
def _open_clip_in_clip_monitor(data):
clip, track, item_id, x = data
media_file = PROJECT().get_media_file_for_path(clip.path)
media_file.mark_in = clip.clip_in
media_file.mark_out = clip.clip_out
updater.set_and_display_monitor_media_file(media_file)
gui.pos_bar.widget.grab_focus()
def _show_clip_info(data):
clip, track, item_id, x = data
width = clip.get("width")
height = clip.get("height")
if clip.media_type == appconsts.IMAGE:
graphic_img = Image.open(clip.path)
width, height = graphic_img.size
size = str(width) + " x " + str(height)
l_frames = clip.clip_out - clip.clip_in + 1 # +1 out inclusive
length = utils.get_tc_string(l_frames)
mark_in = utils.get_tc_string(clip.clip_in)
mark_out = utils.get_tc_string(clip.clip_out + 1) # +1 out inclusive
video_index = clip.get_int("video_index")
audio_index = clip.get_int("audio_index")
long_video_property = "meta.media." + str(video_index) + ".codec.long_name"
long_audio_property = "meta.media." + str(audio_index) + ".codec.long_name"
vcodec = clip.get(str(long_video_property))
acodec = clip.get(str(long_audio_property))
if vcodec == None:
vcodec = _("N/A")
if acodec == None:
acodec = _("N/A")
dialogs.clip_properties_dialog((mark_in, mark_out, length, size, clip.path, vcodec, acodec))
def _rename_clip(data):
clip, track, item_id, x = data
dialogs.new_clip_name_dialog(_rename_clip_edited, clip)
def _rename_clip_edited(dialog, response_id, data):
"""
Sets edited value to liststore and project data.
"""
name_entry, clip = data
new_text = name_entry.get_text()
dialog.destroy()
if response_id != Gtk.ResponseType.ACCEPT:
return
if len(new_text) == 0:
return
clip.name = new_text
updater.repaint_tline()
def _clip_color(data):
clip, track, item_id, clip_color = data
if clip_color == "default":
clip.color = None
elif clip_color == "red":
clip.color = (1, 0, 0)
elif clip_color == "green":
clip.color = (0, 1, 0)
elif clip_color == "blue":
clip.color = (0.2, 0.2, 0.9)
elif clip_color == "orange":
clip.color =(0.929, 0.545, 0.376)
elif clip_color == "brown":
clip.color = (0.521, 0.352, 0.317)
elif clip_color == "olive":
clip.color = (0.5, 0.55, 0.5)
updater.repaint_tline()
def open_selection_in_effects():
if movemodes.selected_range_in == -1:
return
track = get_track(movemodes.selected_track)
clip = track.clips[movemodes.selected_range_in]
clipeffectseditor.set_clip(clip, track, movemodes.selected_range_in)
def _add_filter(data):
clip, track, item_id, item_data = data
x, filter_info = item_data
action = clipeffectseditor.get_filter_add_action(filter_info, clip)
action.do_edit()
# (re)open clip in editor
frame = tlinewidgets.get_frame(x)
index = track.get_clip_index_at(frame)
clipeffectseditor.set_clip(clip, track, index)
def _add_compositor(data):
clip, track, item_id, item_data = data
x, compositor_type = item_data
frame = tlinewidgets.get_frame(x)
clip_index = track.get_clip_index_at(frame)
target_track_index = track.id - 1
if current_sequence().compositing_mode == appconsts.COMPOSITING_MODE_STANDARD_AUTO_FOLLOW:
target_track_index = current_sequence().first_video_index
compositor_in = current_sequence().tracks[track.id].clip_start(clip_index)
clip_length = clip.clip_out - clip.clip_in
compositor_out = compositor_in + clip_length
a_track = target_track_index
b_track = track.id
edit_data = {"origin_clip_id":clip.id,
"in_frame":compositor_in,
"out_frame":compositor_out,
"a_track":a_track,
"b_track":b_track,
"compositor_type":compositor_type,
"clip":clip}
action = edit.add_compositor_action(edit_data)
action.do_edit()
updater.repaint_tline()
def _add_autofade(data):
clip, track, item_id, item_data = data
x, compositor_type = item_data
frame = tlinewidgets.get_frame(x)
clip_index = track.get_clip_index_at(frame)
target_track_index = track.id - 1
clip_length = clip.clip_out - clip.clip_in
if compositor_type == "##auto_fade_in":
compositor_in = current_sequence().tracks[track.id].clip_start(clip_index)
compositor_out = compositor_in + int(utils.fps()) - 1
else:
clip_start = current_sequence().tracks[track.id].clip_start(clip_index)
compositor_out = clip_start + clip_length
compositor_in = compositor_out - int(utils.fps()) + 1
edit_data = {"origin_clip_id":clip.id,
"in_frame":compositor_in,
"out_frame":compositor_out,
"a_track":target_track_index,
"b_track":track.id,
"compositor_type":compositor_type,
"clip":clip}
action = edit.add_compositor_action(edit_data)
action.do_edit()
updater.repaint_tline()
def _re_render_transition_or_fade(data):
clip, track, item_id, item_data = data
from_clip_id, to_clip_id, from_out, from_in, to_out, to_in, transition_type_index, sorted_wipe_luma_index, color_str = clip.creation_data
name, type_id = mlttransitions.rendered_transitions[transition_type_index]
if type_id < appconsts.RENDERED_FADE_IN:
tlineaction.re_render_transition(data)
else:
tlineaction.re_render_fade(data)
def _mute_clip(data):
clip, track, item_id, item_data = data
set_clip_muted = item_data
if set_clip_muted == True:
data = {"clip":clip}
action = edit.mute_clip(data)
action.do_edit()
else:# then we're sitting clip unmuted
data = {"clip":clip}
action = edit.unmute_clip(data)
action.do_edit()
def _delete_clip(data):
tlineaction.splice_out_button_pressed()
def _lift(data):
tlineaction.lift_button_pressed()
def _set_length(data):
clip, track, item_id, item_data = data
dialogs.clip_length_change_dialog(_change_clip_length_dialog_callback, clip, track)
def _change_clip_length_dialog_callback(dialog, response_id, clip, track, length_changer):
if response_id != Gtk.ResponseType.ACCEPT:
dialog.destroy()
return
length = length_changer.get_length()
index = track.clips.index(clip)
dialog.destroy()
data = {"track":track,
"clip":clip,
"index":index,
"length":length}
action = edit.set_clip_length_action(data)
action.do_edit()
def _stretch_next(data):
clip, track, item_id, item_data = data
try:
next_index = track.clips.index(clip) + 1
if next_index >= len( track.clips):
return # clip is last clip
if track.clips[next_index].is_blanck_clip == True:
# Next clip is blank so we can do this.
clip = track.clips[next_index]
data = (clip, track, item_id, item_data)
_cover_blank_from_prev(data, True)
except:
pass # any error means that this can't be done
def _stretch_prev(data):
clip, track, item_id, item_data = data
try:
prev_index = track.clips.index(clip) - 1
if prev_index < 0:
return # clip is first clip
if track.clips[prev_index].is_blanck_clip == True:
# Next clip is blank so we can do this.
clip = track.clips[prev_index]
data = (clip, track, item_id, item_data)
_cover_blank_from_next(data, True)
except:
pass # any error means that this can't be done
def _delete_blank(data):
clip, track, item_id, x = data
movemodes.select_blank_range(track, clip)
from_index = movemodes.selected_range_in
to_index = movemodes.selected_range_out
movemodes.clear_selected_clips()
data = {"track":track,"from_index":from_index,"to_index":to_index}
action = edit.remove_multiple_action(data)
action.do_edit()
def _cover_blank_from_prev(data, called_from_prev_clip=False):
clip, track, item_id, item_data = data
if not called_from_prev_clip:
clip_index = movemodes.selected_range_in - 1
if clip_index < 0: # we're not getting legal clip index
return
cover_clip = track.clips[clip_index]
else:
clip_index = track.clips.index(clip) - 1
cover_clip = track.clips[clip_index]
# Check that clip covers blank area
total_length = 0
for i in range(movemodes.selected_range_in, movemodes.selected_range_out + 1):
total_length += track.clips[i].clip_length()
clip_handle = cover_clip.get_length() - cover_clip.clip_out - 1
if total_length > clip_handle: # handle not long enough to cover blanks
primary_txt = _("Previous clip does not have enough material to cover blank area")
secondary_txt = _("Requested edit can't be done.")
dialogutils.info_message(primary_txt, secondary_txt, gui.editor_window.window)
return
# Do edit
movemodes.clear_selected_clips()
data = {"track":track, "clip":cover_clip, "clip_index":clip_index}
action = edit.trim_end_over_blanks(data)
action.do_edit()
def _cover_blank_from_next(data, called_from_next_clip=False):
clip, track, item_id, item_data = data
if not called_from_next_clip:
clip_index = movemodes.selected_range_out + 1
blank_index = movemodes.selected_range_in
if clip_index < 0: # we are not getting a legal clip index
return
cover_clip = track.clips[clip_index]
else:
clip_index = track.clips.index(clip) + 1
blank_index = clip_index - 1
cover_clip = track.clips[clip_index]
# Check that clip covers blank area
total_length = 0
for i in range(movemodes.selected_range_in, movemodes.selected_range_out + 1):
total_length += track.clips[i].clip_length()
if total_length > cover_clip.clip_in: # handle not long enough to cover blanks
primary_txt = _("Next clip does not have enough material to cover blank area")
secondary_txt = _("Requested edit can't be done.")
dialogutils.info_message(primary_txt, secondary_txt, gui.editor_window.window)
return
# Do edit
movemodes.clear_selected_clips()
data = {"track":track, "clip":cover_clip, "blank_index":blank_index}
action = edit.trim_start_over_blanks(data)
action.do_edit()
def clear_filters():
if movemodes.selected_track == -1:
return
track = get_track(movemodes.selected_track)
clips = []
for i in range(movemodes.selected_range_in, movemodes.selected_range_out + 1):
clips.append(track.clips[i])
data = {"clips":clips}
action = edit.remove_multiple_filters_action(data)
action.do_edit()
movemodes.clear_selected_clips()
updater.repaint_tline()
def _display_wavefrom(data):
audiowaveform.set_waveform_displayer_clip_from_popup(data)
def _clear_waveform(data):
audiowaveform.clear_waveform(data)
def _clone_filters_from_next(data):
clip, track, item_id, item_data = data
index = track.clips.index(clip)
if index == len(track.clips) - 1:
return # clip is last clip
clone_clip = track.clips[index + 1]
_do_filter_clone(clip, clone_clip)
def _clone_filters_from_prev(data):
clip, track, item_id, item_data = data
index = track.clips.index(clip)
if index == 0:
return # clip is first clip
clone_clip = track.clips[index - 1]
_do_filter_clone(clip, clone_clip)
def _do_filter_clone(clip, clone_clip):
if clone_clip.is_blanck_clip:
return
data = {"clip":clip,"clone_source_clip":clone_clip}
action = edit.clone_filters_action(data)
action.do_edit()
def _clear_filters(data):
clip, track, item_id, item_data = data
clear_filters()
def _select_all_after(data):
clip, track, item_id, item_data = data
movemodes._select_multiple_clips(track.id, track.clips.index(clip), len(track.clips) - 1)
updater.repaint_tline()
def _select_all_before(data):
clip, track, item_id, item_data = data
movemodes._select_multiple_clips(track.id, 0, track.clips.index(clip))
updater.repaint_tline()
def _match_frame_start(data):
clip, track, item_id, item_data = data
_set_match_frame(clip, clip.clip_in, track, True)
def _match_frame_end(data):
clip, track, item_id, item_data = data
_set_match_frame(clip, clip.clip_out, track, False)
def _match_frame_start_monitor(data):
clip, track, item_id, item_data = data
gui.monitor_widget.set_frame_match_view(clip, clip.clip_in)
def _match_frame_end_monitor(data):
clip, track, item_id, item_data = data
gui.monitor_widget.set_frame_match_view(clip, clip.clip_out)
def _set_match_frame(clip, frame, track, display_on_right):
global _match_frame_writer
_match_frame_writer = MatchFrameWriter(clip, frame, track, display_on_right)
GLib.idle_add(_write_match_frame)
def _write_match_frame():
_match_frame_writer.write_image()
def _match_frame_close(data):
tlinewidgets.set_match_frame(-1, -1, True)
gui.monitor_widget.set_default_view_force()
updater.repaint_tline()
class MatchFrameWriter:
def __init__(self, clip, clip_frame, track, display_on_right):
self.clip = clip
self.clip_frame = clip_frame
self.track = track
self.display_on_right = display_on_right
def write_image(self):
"""
Writes thumbnail image from file producer
"""
clip_path = self.clip.path
# Create consumer
matchframe_new_path = userfolders.get_cache_dir() + appconsts.MATCH_FRAME_NEW
consumer = mlt.Consumer(PROJECT().profile, "avformat", matchframe_new_path)
consumer.set("real_time", 0)
consumer.set("vcodec", "png")
# Create one frame producer
producer = mlt.Producer(PROJECT().profile, str(clip_path))
producer = producer.cut(int(self.clip_frame), int(self.clip_frame))
# Delete new match frame
try:
os.remove(matchframe_new_path)
except:
# This fails when done first time ever
pass
# Connect and write image
consumer.connect(producer)
consumer.run()
# Wait until new file exists
while os.path.isfile(matchframe_new_path) != True:
time.sleep(0.1)
# Copy to match frame
matchframe_path = userfolders.get_cache_dir() + appconsts.MATCH_FRAME
shutil.copyfile(matchframe_new_path, matchframe_path)
# Update timeline data
# Get frame of clip.clip_in_in on timeline.
clip_index = self.track.clips.index(self.clip)
clip_start_in_tline = self.track.clip_start(clip_index)
tline_match_frame = clip_start_in_tline + (self.clip_frame - self.clip.clip_in)
tlinewidgets.set_match_frame(tline_match_frame, self.track.id, self.display_on_right)
# Update view
updater.repaint_tline()
def _add_clip_marker(data):
clip, track, item_id, item_data = data
current_frame = PLAYER().current_frame()
playhead_on_popup_clip = True
try:
current_frame_clip_index = track.get_clip_index_at(current_frame)
current_frame_clip = track.clips[current_frame_clip_index]
except:
current_frame_clip = None
if current_frame_clip != clip:
# Playhead is not on popup clip
return
clip_start_in_tline = track.clip_start(current_frame_clip_index)
clip_frame = current_frame - clip_start_in_tline + clip.clip_in
dialogs.clip_marker_name_dialog(utils.get_tc_string(clip_frame), utils.get_tc_string(current_frame), _clip_marker_add_dialog_callback, (clip, track, clip_frame))
def _clip_marker_add_dialog_callback(dialog, response_id, name_entry, data):
clip, track, clip_frame = data
name = name_entry.get_text()
dialog.destroy()
# remove older on same frame
dupl_index = -1
for i in range(0, len(clip.markers)):
marker_name, frame = clip.markers[i]
if frame == clip_frame:
dupl_index = i
if dupl_index != -1:
current_sequence().markers.pop(dupl_index)
clip.markers.append((name, clip_frame))
clip.markers = sorted(clip.markers, key=itemgetter(1))
updater.repaint_tline()
def _go_to_clip_marker(data):
clip, track, item_id, item_data = data
marker = clip.markers[int(item_data)]
name, clip_frame = marker
clip_start_in_tline = track.clip_start(track.clips.index(clip))
marker_frame = clip_start_in_tline + clip_frame - clip.clip_in
PLAYER().seek_frame(marker_frame)
def _delete_clip_marker(data):
clip, track, item_id, item_data = data
clip_start_in_tline = track.clip_start(track.clips.index(clip))
current_frame = PLAYER().current_frame()
mrk_index = -1
for i in range(0, len(clip.markers)):
name, marker_clip_frame = clip.markers[i]
marker_tline_frame = clip_start_in_tline + marker_clip_frame - clip.clip_in
if marker_tline_frame == current_frame:
mrk_index = i
if mrk_index != -1:
clip.markers.pop(mrk_index)
updater.repaint_tline()
def _delete_all_clip_markers(data):
clip, track, item_id, item_data = data
clip.markers = []
updater.repaint_tline()
def _volume_keyframes(data):
clip, track, item_id, item_data = data
modesetting.kftool_mode_from_popup_menu(clip, track, kftoolmode.VOLUME_KF_EDIT)
def _brightness_keyframes(data):
clip, track, item_id, item_data = data
modesetting.kftool_mode_from_popup_menu(clip, track, kftoolmode.BRIGHTNESS_KF_EDIT)
# Functions to handle popup menu selections for strings
# set as activation messages in guicomponents.py
# activation_message -> _handler_func
POPUP_HANDLERS = {"set_master":syncsplitevent.init_select_master_clip,
"open_in_editor":_open_clip_in_effects_editor,
"clip_info":_show_clip_info,
"open_in_clip_monitor":_open_clip_in_clip_monitor,
"rename_clip":_rename_clip,
"clip_color":_clip_color,
"split_audio":syncsplitevent.split_audio,
"split_audio_synched":syncsplitevent.split_audio_synched,
"resync":syncsplitevent.resync_clip,
"add_filter":_add_filter,
"add_compositor":_add_compositor,
"clear_sync_rel":syncsplitevent.clear_sync_relation,
"mute_clip":_mute_clip,
"display_waveform":_display_wavefrom,
"clear_waveform":_clear_waveform,
"delete_blank":_delete_blank,
"cover_with_prev": _cover_blank_from_prev,
"cover_with_next": _cover_blank_from_next,
"clone_filters_from_next": _clone_filters_from_next,
"clone_filters_from_prev": _clone_filters_from_prev,
"clear_filters": _clear_filters,
"match_frame_close":_match_frame_close,
"match_frame_start":_match_frame_start,
"match_frame_end":_match_frame_end,
"match_frame_start_monitor":_match_frame_start_monitor,
"match_frame_end_monitor":_match_frame_end_monitor,
"select_all_after": _select_all_after,
"select_all_before":_select_all_before,
"delete":_delete_clip,
"lift":_lift,
"length":_set_length,
"stretch_next":_stretch_next,
"stretch_prev":_stretch_prev,
"add_autofade":_add_autofade,
"set_audio_sync_clip":audiosync.init_select_tline_sync_clip,
"re_render":_re_render_transition_or_fade,
"add_clip_marker":_add_clip_marker,
"go_to_clip_marker":_go_to_clip_marker,
"delete_clip_marker":_delete_clip_marker,
"deleteall_clip_markers":_delete_all_clip_markers,
"volumekf":_volume_keyframes,
"brightnesskf":_brightness_keyframes,
"delete_compositors":_delete_compositors}
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/compositeeditor.py 0000664 0000000 0000000 00000032324 13610327166 0026672 0 ustar 00root root 0000000 0000000 """
Flowblade Movie Editor is a nonlinear video editor.
Copyright 2012 Janne Liljeblad.
This file is part of Flowblade Movie Editor .
Flowblade Movie Editor 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.
Flowblade Movie Editor is distributed in the hope that 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 Flowblade Movie Editor. If not, see .
"""
"""
Module handles Compositors edit panel.
"""
import copy
from gi.repository import Gtk
import pickle
import appconsts
import atomicfile
import compositorfades
import dialogs
import dialogutils
import gui
import guicomponents
import guiutils
import edit
import editorstate
from editorstate import current_sequence
import editorpersistance
import keyframeeditor
import mlttransitions
import propertyeditorbuilder
import propertyedit
import propertyparse
import utils
COMPOSITOR_PANEL_LEFT_WIDTH = 160
widgets = utils.EmptyClass()
compositor = None # Compositor being edited.
# This is updated when filter panel is displayed and cleared when removed.
# Used to update kfeditors with external tline frame position changes
keyframe_editor_widgets = []
compositor_notebook_index = 3 # this is set 2 for the 2 window mode
def create_widgets():
"""
Widgets for editing compositing properties.
"""
widgets.compositor_info = guicomponents.CompositorInfoPanel()
widgets.hamburger_launcher = guicomponents.HamburgerPressLaunch(_hamburger_launch_pressed)
guiutils.set_margins(widgets.hamburger_launcher.widget, 4, 6, 6, 0)
# Edit area
widgets.empty_label = Gtk.Label(label=_("No Compositor"))
widgets.value_edit_box = Gtk.VBox()
widgets.value_edit_box.pack_start(widgets.empty_label, True, True, 0)
widgets.value_edit_frame = Gtk.Frame()
widgets.value_edit_frame.add(widgets.value_edit_box)
widgets.value_edit_frame.set_shadow_type(Gtk.ShadowType.NONE)
def get_compositor_clip_panel():
create_widgets()
# Action row
action_row = Gtk.HBox(False, 2)
action_row.pack_start(widgets.hamburger_launcher.widget, False, False, 0)
action_row.pack_start(Gtk.Label(), True, True, 0)
action_row.pack_start(widgets.compositor_info, False, False, 0)
action_row.pack_start(Gtk.Label(), True, True, 0)
set_enabled(False)
return action_row
def set_compositor(new_compositor):
"""
Sets clip to be edited in compositor editor.
"""
global compositor
if compositor != None and new_compositor.destroy_id != compositor.destroy_id:
compositor.selected = False
compositor = new_compositor
widgets.compositor_info.display_compositor_info(compositor)
set_enabled(True)
_display_compositor_edit_box()
if editorpersistance.prefs.default_layout == True:
gui.middle_notebook.set_current_page(compositor_notebook_index)
def clear_compositor():
global compositor
compositor = None
widgets.compositor_info.set_no_compositor_info()
_display_compositor_edit_box()
set_enabled(False)
def set_enabled(value):
widgets.empty_label.set_sensitive(value)
widgets.compositor_info.set_enabled(value)
widgets.hamburger_launcher.set_sensitive(value)
def maybe_clear_editor(killed_compositor):
if compositor == None:
return
if killed_compositor.destroy_id == compositor.destroy_id:
clear_compositor()
def get_compositor():
return compositor
def _delete_compositor_pressed():
data = {"compositor":compositor}
action = edit.delete_compositor_action(data)
action.do_edit()
def _reset_compositor_pressed():
global compositor
compositor.transition.properties = copy.deepcopy(compositor.transition.info.properties)
propertyparse.replace_value_keywords(compositor.transition.properties, current_sequence().profile)
compositor.transition.update_editable_mlt_properties()
_display_compositor_edit_box()
def _display_compositor_edit_box():
# This gets called on startup before edit_frame is filled
try:
widgets.value_edit_frame.remove(widgets.value_edit_box)
except:
pass
global keyframe_editor_widgets
keyframe_editor_widgets = []
vbox = Gtk.VBox()
# Case: Empty edit frame
global compositor
if compositor == None:
#widgets.empty_label = Gtk.Label(label=_("No Compositor"))
filler = Gtk.EventBox()
filler.add(Gtk.Label())
vbox.pack_start(filler, True, True, 0)
info = Gtk.Label(label=_("No Compositor"))
info.set_sensitive(False)
filler = Gtk.EventBox()
filler.add(info)
vbox.pack_start(filler, False, False, 0)
filler = Gtk.EventBox()
filler.add(Gtk.Label())
vbox.pack_start(filler, True, True, 0)
vbox.show_all()
scroll_window = Gtk.ScrolledWindow()
scroll_window.add_with_viewport(vbox)
scroll_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
scroll_window.show_all()
widgets.value_edit_box = scroll_window
widgets.value_edit_frame.add(scroll_window)
return
# Case: Filled frame
compositor_name_label = Gtk.Label(label= "" + compositor.name + "")
compositor_name_label.set_use_markup(True)
vbox.pack_start(compositor_name_label, False, False, 0)
vbox.pack_start(guicomponents.EditorSeparator().widget, False, False, 0)
# Track editor
if editorstate.get_compositing_mode() != appconsts.COMPOSITING_MODE_STANDARD_AUTO_FOLLOW:
target_combo = guicomponents.get_compositor_track_select_combo(
current_sequence().tracks[compositor.transition.b_track],
current_sequence().tracks[compositor.transition.a_track],
_target_track_changed)
target_row = Gtk.HBox()
target_row.pack_start(guiutils.get_pad_label(5, 3), False, False, 0)
target_row.pack_start(Gtk.Label(label=_("Destination Track:")), False, False, 0)
target_row.pack_start(guiutils.get_pad_label(5, 3), False, False, 0)
target_row.pack_start(target_combo, False, False, 0)
target_row.pack_start(Gtk.Label(), True, True, 0)
vbox.pack_start(target_row, False, False, 0)
vbox.pack_start(guicomponents.EditorSeparator().widget, False, False, 0)
# Transition editors
t_editable_properties = propertyedit.get_transition_editable_properties(compositor)
for ep in t_editable_properties:
editor_row = propertyeditorbuilder.get_editor_row(ep)
if editor_row != None: # Some properties don't have editors
vbox.pack_start(editor_row, False, False, 0)
vbox.pack_start(guicomponents.EditorSeparator().widget, False, False, 0)
# Add keyframe editor widget to be updated for frame changes if such is created.
try:
editor_type = ep.args[propertyeditorbuilder.EDITOR]
except KeyError:
editor_type = propertyeditorbuilder.SLIDER # this is the default value
if ((editor_type == propertyeditorbuilder.KEYFRAME_EDITOR)
or (editor_type == propertyeditorbuilder.KEYFRAME_EDITOR_RELEASE)
or (editor_type == propertyeditorbuilder.KEYFRAME_EDITOR_CLIP)
or (editor_type == propertyeditorbuilder.KEYFRAME_EDITOR_CLIP_FADE)
or (editor_type == propertyeditorbuilder.FADE_LENGTH)
or (editor_type == propertyeditorbuilder.GEOMETRY_EDITOR)):
keyframe_editor_widgets.append(editor_row)
# Extra editors. Editable properties have already been created with "editor=no_editor"
# and will be looked up by editors from clip
editor_rows = propertyeditorbuilder.get_transition_extra_editor_rows(compositor, t_editable_properties)
for editor_row in editor_rows:
# These are added to keyframe editors list based on editor type, not based on EditableProperty type as above
# because one editor sets values for multiple EditableProperty objects
if editor_row.__class__ == keyframeeditor.RotatingGeometryEditor:
keyframe_editor_widgets.append(editor_row)
vbox.pack_start(editor_row, False, False, 0)
vbox.pack_start(guicomponents.EditorSeparator().widget, False, False, 0)
vbox.pack_start(Gtk.Label(), True, True, 0)
vbox.show_all()
scroll_window = Gtk.ScrolledWindow()
scroll_window.add_with_viewport(vbox)
scroll_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
scroll_window.show_all()
widgets.value_edit_box = scroll_window
widgets.value_edit_frame.add(scroll_window)
def _target_track_changed(combo):
if combo.get_active() == 0:
force = True
else:
force = False
a_track = compositor.transition.b_track - combo.get_active() - 1
compositor.transition.set_target_track(a_track, force)
widgets.compositor_info.display_compositor_info(compositor)
def display_kfeditors_tline_frame(frame):
for kf_widget in keyframe_editor_widgets:
kf_widget.display_tline_frame(frame)
def update_kfeditors_sliders(frame):
for kf_widget in keyframe_editor_widgets:
kf_widget.update_slider_value_display(frame)
def update_kfeditors_positions():
for kf_widget in keyframe_editor_widgets:
kf_widget.update_clip_pos()
def _compositor_uses_fade_buttons(compositor):
# we hard coded compositors using fade buttons here because adding data in compostors.xml may have had some backwards compatiblity issues.
if compositor.transition.info.name == "##opacity":
return True
elif compositor.transition.info.name == "##pict_in_pict":
return True
elif compositor.transition.info.name == "##affine":
return True
elif compositor.transition.info.name == "##opacity_kf":
return True
elif compositor.transition.info.name == "##region":
return True
elif compositor.transition.info.name == "##affineblend":
return True
elif compositor.transition.info.name == "##blend":
return True
return False
# ----------------------------------------------------------- hamburger menu
def _hamburger_launch_pressed(widget, event):
guicomponents.get_compositor_editor_hamburger_menu(event, _compositor_hamburger_item_activated)
def _compositor_hamburger_item_activated(widget, msg):
if msg == "save":
comp_name = mlttransitions.name_for_type[compositor.transition.info.name]
default_name = comp_name.replace(" ", "_") + _("_compositor_values") + ".data"
dialogs.save_effects_compositors_values(_save_compositor_values_dialog_callback, default_name, False)
elif msg == "load":
dialogs.load_effects_compositors_values_dialog(_load_compositor_values_dialog_callback, False)
elif msg == "reset":
_reset_compositor_pressed()
elif msg == "delete":
_delete_compositor_pressed()
elif msg == "close":
clear_compositor()
def _save_compositor_values_dialog_callback(dialog, response_id):
if response_id == Gtk.ResponseType.ACCEPT:
save_path = dialog.get_filenames()[0]
compositor_data = CompositorValuesSaveData(compositor.transition.info, compositor.transition.properties)
compositor_data.save(save_path)
dialog.destroy()
def _load_compositor_values_dialog_callback(dialog, response_id):
if response_id == Gtk.ResponseType.ACCEPT:
load_path = dialog.get_filenames()[0]
compositor_data = utils.unpickle(load_path)
if compositor_data.data_applicable(compositor.transition.info):
compositor_data.set_values(compositor)
set_compositor(compositor)
else:
saved_name_comp_name = mlttransitions.name_for_type[compositor_data.info.name]
current_comp_name = mlttransitions.name_for_type[compositor.transition.info.name]
primary_txt = _("Saved Compositor data not applicaple for this compositor!")
secondary_txt = _("Saved data is for ") + saved_name_comp_name + " compositor,\n" + _(", current compositor is ") + current_comp_name + "."
dialogutils.warning_message(primary_txt, secondary_txt, gui.editor_window.window)
dialog.destroy()
class CompositorValuesSaveData:
def __init__(self, info, properties):
self.info = info
self.properties = copy.deepcopy(properties)
def save(self, save_path):
with atomicfile.AtomicFileWriter(save_path, "wb") as afw:
write_file = afw.get_file()
pickle.dump(self, write_file)
def data_applicable(self, compositor_info):
if isinstance(self.info, compositor_info.__class__):
return self.info.__dict__ == compositor_info.__dict__
return False
def set_values(self, compositor):
compositor.transition.properties = copy.deepcopy(self.properties)
compositor.transition.update_editable_mlt_properties()
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/compositorfades.py 0000664 0000000 0000000 00000030361 13610327166 0026661 0 ustar 00root root 0000000 0000000 """
Flowblade Movie Editor is a nonlinear video editor.
Copyright 2012 Janne Liljeblad.
This file is part of Flowblade Movie Editor .
Flowblade Movie Editor 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.
Flowblade Movie Editor is distributed in the hope that 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 Flowblade Movie Editor. If not, see .
"""
import appconsts
import dialogutils
import gui
import editorstate
from editorstate import current_sequence
import propertyedit
import propertyparse
"""
This module handles adding fade-ins and fade-outs to compositors.
Creating and managing keyframes is mostly handled by editor GUI components which cannot easily
be used for adding fade-ins and fade outs, so this dedicated module is needed.
NOTE: This can all (maybe) be killed now and done more sinply in keyframeeditor.py, original reasons for this existing
may not apply anymore.
"""
# Dissolve default fades group ("Dissolve", "Blend") keyframe property class names
_dissolve_property_klasses = ["OpacityInGeomKeyframeProperty", "KeyFrameHCSTransitionProperty"]
# -------------------------------------------------------------- module interface
def add_fade_in(compositor, fade_in_length):
clip = _get_compositor_clip(compositor)
keyframe_property, property_klass, keyframes = _get_kfproperty_klass_and_keyframes(compositor, clip)
if fade_in_length > 0:
if fade_in_length <= clip.clip_length():
return _do_user_add_fade_in(keyframe_property, property_klass, keyframes, fade_in_length)
else:
_show_length_error_dialog()
return None
def add_fade_out(compositor, fade_out_length):
clip = _get_compositor_clip(compositor)
keyframe_property, property_klass, keyframes = _get_kfproperty_klass_and_keyframes(compositor, clip)
if fade_out_length > 0:
if fade_out_length + 1 <= clip.clip_length():
return _do_user_add_fade_out(keyframe_property, property_klass, keyframes, fade_out_length, clip)
else:
_show_length_error_dialog()
return None
def set_auto_fade_in_keyframes(compositor):
clip = _get_compositor_clip(compositor)
keyframe_property, property_klass, keyframes = _get_kfproperty_klass_and_keyframes(compositor, clip)
# Remove all key frames, there exists 2 or 1, 0 when created 1 always after that
while len(keyframes) > 0:
keyframes.pop()
# Set in fade in keyframes
keyframes.append((0, 0))
keyframes.append((compositor.get_length() - 1, 100))
keyframe_property.write_out_keyframes(keyframes)
def set_auto_fade_out_keyframes(compositor):
clip = _get_compositor_clip(compositor)
keyframe_property, property_klass, keyframes = _get_kfproperty_klass_and_keyframes(compositor, clip)
# Remove all key frames, there exists 2 or 1, 0 when created 1 always after that
while len(keyframes) > 0:
keyframes.pop()
# Set in fade in keyframes
keyframes.append((0, 100))
keyframes.append((compositor.get_length() - 1, 0))
keyframe_property.write_out_keyframes(keyframes)
# ---------------------------------------------------------------------- module functions
def _get_kfproperty_klass_and_keyframes(compositor, clip):
# We create a SECOND SET of EditableProperties from compositor properties.
# These are not the same EditableProperties that are edited in GUI in "Compositor" panel.
# This approach seems necessery because Affine Blend requires creating a new property.
t_editable_properties = propertyedit.get_transition_editable_properties(compositor)
# Find keyframe property, its class and create keyframes list
if compositor.transition.info.mlt_service_id == "frei0r.cairoaffineblend": # Affine Blend
# Because of frei0r's forced value 0.0-1.0 range "Affine Blend" is handled in a more complex way compared to other compositors
keyframe_property = propertyparse.create_editable_property_for_affine_blend(clip, t_editable_properties)
keyframes = propertyparse.rotating_geom_keyframes_value_string_to_geom_kf_array(keyframe_property.value, keyframe_property.get_in_value)
property_klass = keyframe_property.__class__.__name__
return (keyframe_property, property_klass, keyframes)
else: # Dissolve, Blend, Picture-in-Picture, Region
keyframe_property = None
property_klass = None
for ep in t_editable_properties:
property_klass = ep.__class__.__name__
if property_klass == "OpacityInGeomKeyframeProperty": # Dissolve
keyframe_property = ep
keyframes = propertyparse.geom_keyframes_value_string_to_opacity_kf_array(keyframe_property.value, keyframe_property.get_in_value)
break
if property_klass == "KeyFrameHCSTransitionProperty" and compositor.transition.info.mlt_service_id != "affine": # Blend, and we exclude Transform
keyframe_property = ep
keyframes = propertyparse.single_value_keyframes_string_to_kf_array(keyframe_property.value, keyframe_property.get_in_value)
break
if property_klass == "KeyFrameGeometryOpacityProperty": # Picture-in-Picture, Region
keyframe_property = ep
keyframes = propertyparse.geom_keyframes_value_string_to_geom_kf_array(keyframe_property.value, keyframe_property.get_in_value)
break
if keyframe_property == None:
#print "didn't find keyframe_property in _get_kfproperty_klass_and_keyframes"
return (None, None, None)
return (keyframe_property, property_klass, keyframes)
def _get_compositor_clip(compositor):
for i in range(current_sequence().first_video_index, len(current_sequence().tracks) - 1): # -1, there is a topmost hidden track
track = current_sequence().tracks[i] # b_track is source track where origin clip is
for j in range(0, len(track.clips)):
clip = track.clips[j]
if clip.id == compositor.origin_clip_id:
return clip
return None
def _get_default_fades_lengths(property_klass):
if property_klass in _dissolve_property_klasses:
fade_in_length = editorstate.PROJECT().get_project_property(appconsts.P_PROP_DISSOLVE_GROUP_FADE_IN)
fade_out_length = editorstate.PROJECT().get_project_property(appconsts.P_PROP_DISSOLVE_GROUP_FADE_OUT)
else:
fade_in_length = editorstate.PROJECT().get_project_property(appconsts.P_PROP_ANIM_GROUP_FADE_IN)
fade_out_length = editorstate.PROJECT().get_project_property(appconsts.P_PROP_ANIM_GROUP_FADE_OUT)
return (fade_in_length, fade_out_length)
def _add_default_fade_in(keyframe_property, property_klass, keyframes, fade_in_length):
if property_klass in _dissolve_property_klasses:
frame, opacity = keyframes.pop(0)
keyframes.append((frame, 0))
keyframes.append((frame + fade_in_length, 100))
return keyframes
else:
# (0, [0, 0, 1280, 720], 100.0) or (0, [640.0, 360.0, 1.0, 1.0, 0.0], 100.0) e.g.
frame, geom, opacity = keyframes.pop(0)
keyframes.append((frame, geom, 0))
keyframes.append((frame + fade_in_length, geom, 100))
return keyframes
def _add_default_fade_out(keyframe_property, property_klass, keyframes, fade_out_length, clip, kf_before_fade_out_index=0):
if property_klass in _dissolve_property_klasses:
keyframes.append((clip.clip_length() - fade_out_length - 1, 100))
keyframes.append((clip.clip_length() - 1, 0))
return keyframes
else:
# (0, [0, 0, 1280, 720], 100.0) or (0, [640.0, 360.0, 1.0, 1.0, 0.0], 100.0) e.g.
frame, geom, opacity = keyframes[kf_before_fade_out_index]
keyframes.append((clip.clip_length() - fade_out_length - 1, geom, 100))
keyframes.append((clip.clip_length() - 1, geom, 0))
return keyframes
def _do_user_add_fade_in(keyframe_property, property_klass, keyframes, fade_in_length):
# Get index of first keyframe after fade_in_length
kf_after_fade_in_index = -1
for i in range (0, len(keyframes)):
kf = keyframes[i]
if property_klass in _dissolve_property_klasses:
frame, opacity = kf
else:
frame, geom, opacity = kf
if frame > fade_in_length:
kf_after_fade_in_index = i
break
# Case no keyframes after fade in length
if kf_after_fade_in_index == -1:
# Remove all but first keyframe
for i in range(0, len(keyframes) - 1):
keyframes.pop(1)
# nOw this the same action as addin default keyframe on creation
keyframes = _add_default_fade_in(keyframe_property, property_klass, keyframes, fade_in_length)
# Case keyframes exists after fade in length
else:
# Remove keyframes in range 0 - kf_after_fade_in_index
for i in range(0, kf_after_fade_in_index - 1):
keyframes.pop(1)
if property_klass in _dissolve_property_klasses:
frame, opacity = keyframes.pop(0)
keyframes.insert(0, (frame, 0))
keyframes.insert(1,(frame + fade_in_length, 100))
else:
# (0, [0, 0, 1280, 720], 100.0) or (0, [640.0, 360.0, 1.0, 1.0, 0.0], 100.0) e.g.
frame, geom, opacity = keyframes.pop(0)
keyframes.insert(0, (frame, geom, 0))
keyframes.insert(1, (frame + fade_in_length, geom, 100))
# Because we created a SECOND SET of EditableProperties this only updates data structures (py and MLT)
# but not EditableProperties wrappers that are edited in GUI in "Compositor" panel.
keyframe_property.write_out_keyframes(keyframes)
# We need to return updated keyframes to update GUI in "Compositor" panel.
return keyframes
def _do_user_add_fade_out(keyframe_property, property_klass, keyframes, fade_out_length, clip):
# Get index of first keyframe before fade out begins
fade_out_frame = clip.clip_length() - fade_out_length
kf_after_fade_out_index = -1
for i in range (0, len(keyframes)):
kf = keyframes[i]
if property_klass in _dissolve_property_klasses:
frame, opacity = kf
else:
frame, geom, opacity = kf
if frame >= fade_out_frame:
kf_after_fade_out_index = i
break
# Case no keyframes after fade out start
if kf_after_fade_out_index == -1:
keyframes = _add_default_fade_out(keyframe_property, property_klass, keyframes, fade_out_length, clip, 0)
# Case keyframes exists after fade out start
else:
# Remove keyframes in range 0 - kf_after_fade_in_index
for i in range(kf_after_fade_out_index, len(keyframes)):
keyframes.pop(-1) # pop last
keyframes = _add_default_fade_out(keyframe_property, property_klass, keyframes, fade_out_length, clip, len(keyframes) - 1)
# Because we created a SECOND SET of EditableProperties this only updates data structures (py and MLT)
# but not EditableProperties wrappers that are edited in GUI in "Compositor" panel.
keyframe_property.write_out_keyframes(keyframes)
# We need to return updated keyframes to update GUI in "Compositor" panel.
return keyframes
def _show_length_error_dialog():
parent_window = gui.editor_window.window
primary_txt = _("Clip too short!")
secondary_txt = _("The Clip is too short to add the requested fade.")
dialogutils.info_message(primary_txt, secondary_txt, parent_window)
def _show_defaults_length_error_dialog():
parent_window = gui.editor_window.window
primary_txt = _("Clip too short for Auto Fades!")
secondary_txt = _("The Clip is too short to add the user set default fades on Compositor creation.")
dialogutils.info_message(primary_txt, secondary_txt, parent_window)
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/compositormodes.py 0000664 0000000 0000000 00000015025 13610327166 0026706 0 ustar 00root root 0000000 0000000 """
Flowblade Movie Editor is a nonlinear video editor.
Copyright 2012 Janne Liljeblad.
This file is part of Flowblade Movie Editor .
Flowblade Movie Editor 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.
Flowblade Movie Editor is distributed in the hope that 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 Flowblade Movie Editor. If not, see .
"""
"""
Module handles editing positions and clip ends of compositors on timeline.
"""
import appconsts
import gui
import edit
import editorstate
from editorstate import current_sequence
import tlinewidgets
import updater
# mouse press area to trim instead of move
TRIM_HANDLE_WIDTH = 10
# modes
MOVE_EDIT = 0
TRIM_EDIT = 1
NO_COMPOSITOR_EDIT = -1 # used to block deleting compositor while editing
# module globals
compositor = None
edit_data = None
sub_mode = NO_COMPOSITOR_EDIT
prev_edit_mode = None
def set_compositor_mode(new_compositor):
global prev_edit_mode
prev_edit_mode = editorstate.EDIT_MODE()
editorstate.edit_mode = editorstate.COMPOSITOR_EDIT
set_compositor_selected(new_compositor)
def set_compositor_selected(new_compositor):
global compositor
if compositor != None:
compositor.selected = False
compositor = new_compositor
compositor.selected = True
def clear_compositor_selection():
global compositor
if compositor == None:
return
compositor.selected = False
compositor = None
def delete_current_selection():
global compositor
if compositor == None:
return
if sub_mode != NO_COMPOSITOR_EDIT:
return
data = {"compositor":compositor}
action = edit.delete_compositor_action(data)
action.do_edit()
compositor.selected = False # this may return in undo?
compositor = None
def mouse_press(event, frame):
track = current_sequence().tracks[compositor.transition.b_track - 1]
global edit_data, sub_mode
compositor_y = tlinewidgets._get_track_y(track.id) - tlinewidgets.COMPOSITOR_HEIGHT_OFF
if abs(event.x - tlinewidgets._get_frame_x(compositor.clip_in)) < TRIM_HANDLE_WIDTH:
edit_data = {"clip_in":compositor.clip_in,
"clip_out":compositor.clip_out,
"trim_is_clip_in":True,
"orig_clip_in":compositor.clip_in,
"compositor_y": compositor_y,
"compositor": compositor}
tlinewidgets.set_edit_mode(edit_data, tlinewidgets.draw_compositor_trim)
sub_mode = TRIM_EDIT
elif abs(event.x - tlinewidgets._get_frame_x(compositor.clip_out + 1)) < TRIM_HANDLE_WIDTH:
edit_data = {"clip_in":compositor.clip_in,
"clip_out":compositor.clip_out,
"trim_is_clip_in":False,
"orig_clip_out":compositor.clip_out,
"compositor_y": compositor_y,
"compositor": compositor}
tlinewidgets.set_edit_mode(edit_data, tlinewidgets.draw_compositor_trim)
sub_mode = TRIM_EDIT
else:
edit_data = {"press_frame":frame,
"current_frame":frame,
"clip_in":compositor.clip_in,
"clip_length":(compositor.clip_out - compositor.clip_in + 1),
"compositor_y": compositor_y,
"compositor": compositor}
tlinewidgets.set_edit_mode(edit_data, tlinewidgets.draw_compositor_move_overlay)
sub_mode = MOVE_EDIT
updater.repaint_tline()
def get_pointer_context(compositor, x):
if abs(x - tlinewidgets._get_frame_x(compositor.clip_in)) < TRIM_HANDLE_WIDTH:
return appconsts.POINTER_CONTEXT_COMPOSITOR_END_DRAG_LEFT
elif abs(x - tlinewidgets._get_frame_x(compositor.clip_out + 1)) < TRIM_HANDLE_WIDTH:
return appconsts.POINTER_CONTEXT_COMPOSITOR_END_DRAG_RIGHT
else:
return appconsts.POINTER_CONTEXT_COMPOSITOR_MOVE
def mouse_move(x, y, frame, state):
global edit_data
if sub_mode == TRIM_EDIT:
_bounds_check_trim(frame, edit_data)
else:
edit_data["current_frame"] = frame
updater.repaint_tline()
def mouse_release(x, y, frame, state):
global sub_mode
tlinewidgets.pointer_context = appconsts.POINTER_CONTEXT_NONE
editorstate.edit_mode = prev_edit_mode
if editorstate.edit_mode == editorstate.INSERT_MOVE:
tlinewidgets.set_edit_mode(None, tlinewidgets.draw_insert_overlay)
elif editorstate.edit_mode == editorstate.OVERWRITE_MOVE:
tlinewidgets.set_edit_mode(None, tlinewidgets.draw_overwrite_overlay)
elif editorstate.edit_mode == editorstate.MULTI_MOVE:
tlinewidgets.set_edit_mode(None, tlinewidgets.draw_multi_overlay)
else:
print("COMPOSITOR MODE EXIT PROBLEM at compositormodes.mouse_release")
gui.editor_window.set_cursor_to_mode()
if sub_mode == TRIM_EDIT:
_bounds_check_trim(frame, edit_data)
data = {"compositor":compositor,
"clip_in":edit_data["clip_in"],
"clip_out":edit_data["clip_out"]}
action = edit.move_compositor_action(data)
action.do_edit()
else:
press_frame = edit_data["press_frame"]
current_frame = frame
delta = current_frame - press_frame
data = {"compositor":compositor,
"clip_in":compositor.clip_in + delta,
"clip_out":compositor.clip_out + delta}
if data["clip_in"] < 0:
data["clip_in"] = 0
if data["clip_out"] < 0:
data["clip_out"] = 0
action = edit.move_compositor_action(data)
action.do_edit()
sub_mode = NO_COMPOSITOR_EDIT
updater.repaint_tline()
def _bounds_check_trim(frame, edit_data):
if edit_data["trim_is_clip_in"] == True:
if frame > edit_data["clip_out"]:
frame = edit_data["clip_out"]
edit_data["clip_in"] = frame
else:
if frame < edit_data["clip_in"]:
frame = edit_data["clip_in"]
edit_data["clip_out"] = frame
if edit_data["clip_in"] < 0:
edit_data["clip_in"] = 0
if edit_data["clip_out"] < 0:
edit_data["clip_out"] = 0
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/cutmode.py 0000664 0000000 0000000 00000005772 13610327166 0025130 0 ustar 00root root 0000000 0000000 """
Flowblade Movie Editor is a nonlinear video editor.
Copyright 2012 Janne Liljeblad.
This file is part of Flowblade Movie Editor .
Flowblade Movie Editor 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.
Flowblade Movie Editor is distributed in the hope that 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 Flowblade Movie Editor. If not, see .
"""
"""
Module handles cut tool functionality
"""
from gi.repository import Gdk
import appconsts
import dialogutils
import edit
from editorstate import current_sequence
import tlinewidgets
import updater
# ---------------------------------------------- mouse events
def mouse_press(event, frame):
if not (event.get_state() & Gdk.ModifierType.CONTROL_MASK):
cut_single_track(event, frame)
else:
cut_all_tracks(frame)
def mouse_move(x, y, frame, state):
pass
def mouse_release(x, y, frame, state):
pass
# ---------------------------------------------- cut actions
def cut_single_track(event, frame):
track = tlinewidgets.get_track(event.y)
if track == None or track.id == 0 or track.id == len(current_sequence().tracks) - 1:
return
if dialogutils.track_lock_check_and_user_info(track):
return
data = get_cut_data(track, frame)
if data == None:
return
action = edit.cut_action(data)
action.do_edit()
updater.repaint_tline()
def cut_all_tracks(frame):
tracks_cut_data = []
for i in range(1, len(current_sequence().tracks) - 1):
if current_sequence().tracks[i].edit_freedom == appconsts.LOCKED:
tracks_cut_data.append(None) # Don't cut locked tracks.
else:
tracks_cut_data.append(get_cut_data(current_sequence().tracks[i], frame))
data = {"tracks_cut_data":tracks_cut_data}
action = edit.cut_all_action(data)
action.do_edit()
updater.repaint_tline()
def get_cut_data(track, frame):
# Get index and clip
index = track.get_clip_index_at(int(frame))
try:
clip = track.clips[index]
# don't cut blanck clip
if clip.is_blanck_clip:
return None
except Exception:
return None
# Get cut frame in clip frames
clip_start_in_tline = track.clip_start(index)
clip_frame = frame - clip_start_in_tline + clip.clip_in
# No data if frame on cut.
if clip_frame == clip.clip_in:
return None
data = {"track":track,
"index":index,
"clip":clip,
"clip_cut_frame":clip_frame}
return data
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/dialogs.py 0000664 0000000 0000000 00000255626 13610327166 0025117 0 ustar 00root root 0000000 0000000 """
Flowblade Movie Editor is a nonlinear video editor.
Copyright 2012 Janne Liljeblad.
This file is part of Flowblade Movie Editor .
Flowblade Movie Editor 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.
Flowblade Movie Editor is distributed in the hope that 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 Flowblade Movie Editor. If not, see .
"""
"""
Module builds dialog windows. User input is handled at
callsites which provide callback methods for response signals.
"""
from gi.repository import Gtk
import locale
import os
from gi.repository import Pango
import appconsts
import dialogutils
import gui
import guicomponents
import guiutils
import editorstate
import editorpersistance
import mltenv
import mltprofiles
import mltfilters
import mlttransitions
import panels
import renderconsumer
import respaths
import shortcuts
import utils
import workflow
def new_project_dialog(callback):
default_profile_index = mltprofiles.get_default_profile_index()
default_profile = mltprofiles.get_default_profile()
dialog = Gtk.Dialog(_("New Project"), gui.editor_window.window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Cancel"), Gtk.ResponseType.REJECT,
_("OK"), Gtk.ResponseType.ACCEPT))
out_profile_combo = Gtk.ComboBoxText()
profiles = mltprofiles.get_profiles()
for profile in profiles:
out_profile_combo.append_text(profile[0])
out_profile_combo.set_active(default_profile_index)
profile_select = panels.get_two_column_box(Gtk.Label(label=_("Project profile:")),
out_profile_combo,
250)
profile_info_panel = guicomponents.get_profile_info_box(default_profile, False)
profile_info_box = Gtk.VBox()
profile_info_box.add(profile_info_panel)
profiles_vbox = guiutils.get_vbox([profile_select,profile_info_box], False)
profiles_frame = panels.get_named_frame(_("Profile"), profiles_vbox)
tracks_select = guicomponents.TracksNumbersSelect(appconsts.INIT_V_TRACKS, appconsts.INIT_A_TRACKS)
tracks_vbox = guiutils.get_vbox([tracks_select.widget], False)
tracks_frame = panels.get_named_frame(_("Tracks"), tracks_vbox)
vbox = guiutils.get_vbox([profiles_frame, tracks_frame], False)
alignment = dialogutils.get_default_alignment(vbox)
dialogutils.set_outer_margins(dialog.vbox)
dialog.vbox.pack_start(alignment, True, True, 0)
_default_behaviour(dialog)
dialog.connect('response', callback, out_profile_combo, tracks_select)
out_profile_combo.connect('changed', lambda w: _new_project_profile_changed(w, profile_info_box))
dialog.show_all()
def xdg_copy_dialog():
dialog = Gtk.Dialog(_("New Project"), gui.editor_window.window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
None)
primary_txt = _("Copying user data to XDG folders")
secondary_txt = _("This can take up to a few minutes, please wait...")
panel = dialogutils.get_warning_message_dialog_panel(primary_txt, secondary_txt, is_info=True)
alignment = dialogutils.get_default_alignment(panel)
dialogutils.set_outer_margins(dialog.vbox)
dialog.vbox.pack_start(alignment, True, True, 0)
dialog.show_all()
return dialog
def _new_project_profile_changed(combo_box, profile_info_box):
profile = mltprofiles.get_profile_for_index(combo_box.get_active())
info_box_children = profile_info_box.get_children()
for child in info_box_children:
profile_info_box.remove(child)
info_panel = guicomponents.get_profile_info_box(profile, True)
profile_info_box.add(info_panel)
profile_info_box.show_all()
info_panel.show()
def change_profile_project_dialog(project, callback):
project_name = project.name.rstrip(".flb")
default_profile_index = mltprofiles.get_index_for_name(project.profile.description())
default_profile = mltprofiles.get_default_profile()
dialog = Gtk.Dialog(_("Change Project Profile"), gui.editor_window.window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Cancel"), Gtk.ResponseType.REJECT,
_("Save With Changed Profile"), Gtk.ResponseType.ACCEPT))
info_label = guiutils.bold_label(_("Project Profile can only changed by saving a version\nwith different profile."))
out_profile_combo = Gtk.ComboBoxText()
profiles = mltprofiles.get_profiles()
for profile in profiles:
out_profile_combo.append_text(profile[0])
out_profile_combo.set_active(default_profile_index)
profile_select = panels.get_two_column_box(Gtk.Label(label=_("Project profile:")),
out_profile_combo,
250)
profile_info_panel = guicomponents.get_profile_info_box(default_profile, False)
profile_info_box = Gtk.VBox()
profile_info_box.add(profile_info_panel)
profiles_vbox = guiutils.get_vbox([profile_select,profile_info_box], False)
profiles_frame = panels.get_named_frame(_("New Profile"), profiles_vbox)
out_folder = Gtk.FileChooserButton(_("Select Folder"))
out_folder.set_action(Gtk.FileChooserAction.SELECT_FOLDER)
out_folder.set_current_folder(os.path.expanduser("~") + "/")
out_folder.set_local_only(True)
out_folder_row = panels.get_two_column_box(Gtk.Label(label=_("Folder:")), out_folder, 250)
project_name_entry = Gtk.Entry()
project_name_entry.set_text(project_name + "_NEW_PROFILE.flb")
extension_label = Gtk.Label()
name_box = Gtk.HBox(False, 8)
name_box.pack_start(project_name_entry, True, True, 0)
movie_name_row = panels.get_two_column_box(Gtk.Label(label=_("Project Name:")), name_box, 250)
new_file_vbox = guiutils.get_vbox([out_folder_row, movie_name_row], False)
new_file_frame = panels.get_named_frame(_("New Project File"), new_file_vbox)
vbox = guiutils.get_vbox([info_label, guiutils.pad_label(2, 24), profiles_frame, new_file_frame], False)
alignment = dialogutils.get_default_alignment(vbox)
dialogutils.set_outer_margins(dialog.vbox)
dialog.vbox.pack_start(alignment, True, True, 0)
_default_behaviour(dialog)
dialog.connect('response', callback, out_profile_combo, out_folder, project_name_entry)#, project_type_combo,
#project_folder, compact_name_entry)
out_profile_combo.connect('changed', lambda w: _new_project_profile_changed(w, profile_info_box))
dialog.show_all()
def change_profile_project_to_match_media_dialog(project, media_file, callback):
project_name = project.name.rstrip(".flb")
default_profile_index = mltprofiles.get_index_for_name(project.profile.description())
default_profile = mltprofiles.get_default_profile()
dialog = Gtk.Dialog(_("Change Project Profile"), gui.editor_window.window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Cancel"), Gtk.ResponseType.REJECT,
_("Save With Changed Profile"), Gtk.ResponseType.ACCEPT))
info_label = guiutils.bold_label(_("Project Profile can only changed by saving a version\nwith different profile."))
match_profile_index = mltprofiles.get_closest_matching_profile_index(media_file.info)
match_profile_name = mltprofiles.get_profile_name_for_index(match_profile_index)
project_profile_name = project.profile.description()
row1 = guiutils.get_two_column_box(guiutils.bold_label(_("File:")), Gtk.Label(label=media_file.name), 120)
row2 = guiutils.get_two_column_box(guiutils.bold_label(_("File Best Match Profile:")), Gtk.Label(label=match_profile_name), 120)
row3 = guiutils.get_two_column_box(guiutils.bold_label(_("Project Current Profile:")), Gtk.Label(label=project_profile_name), 120)
text_panel = Gtk.VBox(False, 2)
text_panel.pack_start(row1, False, False, 0)
text_panel.pack_start(row2, False, False, 0)
text_panel.pack_start(row3, False, False, 0)
out_folder = Gtk.FileChooserButton(_("Select Folder"))
out_folder.set_action(Gtk.FileChooserAction.SELECT_FOLDER)
out_folder.set_current_folder(os.path.expanduser("~") + "/")
out_folder.set_local_only(True)
out_folder_row = panels.get_two_column_box(Gtk.Label(label=_("Folder:")), out_folder, 250)
project_name_entry = Gtk.Entry()
project_name_entry.set_text(project_name + "_NEW_PROFILE.flb")
extension_label = Gtk.Label()
name_box = Gtk.HBox(False, 8)
name_box.pack_start(project_name_entry, True, True, 0)
movie_name_row = panels.get_two_column_box(Gtk.Label(label=_("Project Name:")), name_box, 250)
new_file_vbox = guiutils.get_vbox([out_folder_row, movie_name_row], False)
new_file_frame = panels.get_named_frame(_("New Project File"), new_file_vbox)
save_profile_info = guiutils.bold_label(_("Project will be saved with profile: ") + match_profile_name)
vbox = guiutils.get_vbox([info_label, guiutils.pad_label(2, 24), text_panel, \
guiutils.pad_label(2, 24), save_profile_info, guiutils.pad_label(2, 24), \
new_file_frame], False)
alignment = dialogutils.get_default_alignment(vbox)
dialogutils.set_outer_margins(dialog.vbox)
dialog.vbox.pack_start(alignment, True, True, 0)
_default_behaviour(dialog)
dialog.connect('response', callback, match_profile_index, out_folder, project_name_entry)
dialog.show_all()
def save_backup_snapshot(name, callback):
dialog = Gtk.Dialog(_("Save Project Backup Snapshot"), gui.editor_window.window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Cancel"), Gtk.ResponseType.REJECT,
_("OK"), Gtk.ResponseType.ACCEPT))
project_folder = Gtk.FileChooserButton(_("Select Snapshot Project Folder"))
project_folder.set_action(Gtk.FileChooserAction.SELECT_FOLDER)
project_folder.set_current_folder(os.path.expanduser("~") + "/")
project_folder_label = Gtk.Label(label=_("Snapshot Folder:"))
project_folder_row = guiutils.get_two_column_box(project_folder_label, project_folder, 250)
compact_name_entry = Gtk.Entry.new()
compact_name_entry.set_width_chars(30)
compact_name_entry.set_text(name)
compact_name_label = Gtk.Label(label=_("Project File Name:"))
compact_name_entry_row = guiutils.get_two_column_box(compact_name_label, compact_name_entry, 250)
type_vbox = Gtk.VBox(False, 2)
type_vbox.pack_start(project_folder_row, False, False, 0)
type_vbox.pack_start(compact_name_entry_row, False, False, 0)
vbox = Gtk.VBox(False, 2)
vbox.add(type_vbox)
alignment = dialogutils.get_default_alignment(vbox)
dialog.vbox.pack_start(alignment, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
_default_behaviour(dialog)
dialog.connect('response', callback, project_folder, compact_name_entry)
dialog.show_all()
def export_ardour_session_folder_select(callback):
dialog = Gtk.Dialog(_("Save Sequence Audio As Ardour Session"), gui.editor_window.window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Cancel"), Gtk.ResponseType.REJECT,
_("Export"), Gtk.ResponseType.ACCEPT))
project_folder = Gtk.FileChooserButton(_("Select Ardour Session Folder"))
project_folder.set_action(Gtk.FileChooserAction.SELECT_FOLDER)
project_folder.set_current_folder(os.path.expanduser("~") + "/")
project_folder_label = Gtk.Label(label=_("Select Ardour Session Folder:"))
project_folder_row = guiutils.get_two_column_box(project_folder_label, project_folder, 250)
type_vbox = Gtk.VBox(False, 2)
type_vbox.pack_start(project_folder_row, False, False, 0)
vbox = Gtk.VBox(False, 2)
vbox.add(type_vbox)
alignment = dialogutils.get_default_alignment(vbox)
dialog.vbox.pack_start(alignment, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
_default_behaviour(dialog)
dialog.connect('response', callback, project_folder)
dialog.show_all()
def load_project_dialog(callback, parent=None, title_text=None):
if parent == None:
parent = gui.editor_window.window
if title_text == None:
title_text = _("Select Project File")
dialog = Gtk.FileChooserDialog(title_text, parent,
Gtk.FileChooserAction.OPEN,
(_("Cancel"), Gtk.ResponseType.CANCEL,
_("OK"), Gtk.ResponseType.ACCEPT))
dialog.set_action(Gtk.FileChooserAction.OPEN)
dialog.set_select_multiple(False)
file_filter = Gtk.FileFilter()
file_filter.set_name(_("Flowblade Projects"))
file_filter.add_pattern("*" + appconsts.PROJECT_FILE_EXTENSION)
dialog.add_filter(file_filter)
dialog.connect('response', callback)
dialog.show()
def save_project_as_dialog(callback, current_name, open_dir, parent=None):
if parent == None:
parent = gui.editor_window.window
dialog = Gtk.FileChooserDialog(_("Save Project As"), parent,
Gtk.FileChooserAction.SAVE,
(_("Cancel"), Gtk.ResponseType.CANCEL,
_("Save"), Gtk.ResponseType.ACCEPT))
dialog.set_action(Gtk.FileChooserAction.SAVE)
dialog.set_current_name(current_name)
dialog.set_do_overwrite_confirmation(True)
if open_dir != None:
dialog.set_current_folder(open_dir)
dialog.set_select_multiple(False)
file_filter = Gtk.FileFilter()
file_filter.add_pattern("*" + appconsts.PROJECT_FILE_EXTENSION)
dialog.add_filter(file_filter)
dialog.connect('response', callback)
dialog.show()
def save_effects_compositors_values(callback, default_name, saving_effect=True):
parent = gui.editor_window.window
if saving_effect == True:
title = _("Save Effect Values Data")
else:
title = _("Save Compositor Values Data")
dialog = Gtk.FileChooserDialog(title, parent,
Gtk.FileChooserAction.SAVE,
(_("Cancel"), Gtk.ResponseType.CANCEL,
_("Save"), Gtk.ResponseType.ACCEPT))
dialog.set_action(Gtk.FileChooserAction.SAVE)
dialog.set_current_name(default_name)
dialog.set_do_overwrite_confirmation(True)
dialog.set_select_multiple(False)
file_filter = Gtk.FileFilter()
file_filter.set_name(_("Effect/Compositor Values Data"))
file_filter.add_pattern("*" + "data")
dialog.add_filter(file_filter)
dialog.connect('response', callback)
dialog.show()
def load_effects_compositors_values_dialog(callback, loading_effect=True):
parent = gui.editor_window.window
if loading_effect == True:
title_text = _("Load Effect Values Data")
else:
title_text = _("Load Compositor Values Data")
dialog = Gtk.FileChooserDialog(title_text, parent,
Gtk.FileChooserAction.OPEN,
(_("Cancel"), Gtk.ResponseType.CANCEL,
_("OK"), Gtk.ResponseType.ACCEPT))
dialog.set_action(Gtk.FileChooserAction.OPEN)
dialog.set_select_multiple(False)
file_filter = Gtk.FileFilter()
file_filter.set_name(_("Effect/Compositor Values Data"))
file_filter.add_pattern("*" + "data")
dialog.add_filter(file_filter)
dialog.connect('response', callback)
dialog.show()
def export_xml_dialog(callback, project_name):
_export_file_name_dialog(callback, project_name, _("Export Project as XML to"))
def _export_file_name_dialog(callback, project_name, dialog_title):
dialog = Gtk.FileChooserDialog(dialog_title, gui.editor_window.window,
Gtk.FileChooserAction.SAVE,
(_("Cancel"), Gtk.ResponseType.CANCEL,
_("Export"), Gtk.ResponseType.ACCEPT))
dialog.set_action(Gtk.FileChooserAction.SAVE)
project_name = project_name.strip(".flb")
dialog.set_current_name(project_name + ".xml")
dialog.set_do_overwrite_confirmation(True)
dialog.set_select_multiple(False)
dialog.connect('response', callback)
dialog.show()
def compound_clip_name_dialog(callback, default_name, dialog_title, data=None):
dialog = Gtk.Dialog(dialog_title, gui.editor_window.window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Cancel"), Gtk.ResponseType.REJECT,
_("Create"), Gtk.ResponseType.ACCEPT))
name_entry = Gtk.Entry()
name_entry.set_width_chars(30)
name_entry.set_text(default_name)
name_entry.set_activates_default(True)
name_select = panels.get_two_column_box(Gtk.Label(label=_("Clip Name:")),
name_entry,
180)
vbox = Gtk.VBox(False, 2)
vbox.pack_start(name_select, False, False, 0)
vbox.pack_start(guiutils.get_pad_label(12, 12), False, False, 0)
alignment = dialogutils.get_alignment2(vbox)
dialog.vbox.pack_start(alignment, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
_default_behaviour(dialog)
dialog.set_default_response(Gtk.ResponseType.ACCEPT)
if data == None:
dialog.connect('response', callback, name_entry)
else:
dialog.connect('response', callback, (data, name_entry))
dialog.show_all()
def save_env_data_dialog(callback):
dialog = Gtk.FileChooserDialog(_("Save Runtime Environment Data"), gui.editor_window.window,
Gtk.FileChooserAction.SAVE,
(_("Cancel"), Gtk.ResponseType.CANCEL,
_("Save"), Gtk.ResponseType.ACCEPT))
dialog.set_action(Gtk.FileChooserAction.SAVE)
dialog.set_current_name("flowblade_runtime_environment_data")
dialog.set_do_overwrite_confirmation(True)
dialog.set_select_multiple(False)
dialog.connect('response', callback)
dialog.show()
def rendered_clips_no_home_folder_dialog():
dialogutils.warning_message(_("Can't make home folder render clips folder"),
_("Please create and select some other folder then \'") +
os.path.expanduser("~") + _("\' as render clips folder"),
gui.editor_window.window)
def exit_confirm_dialog(callback, msg, parent_window, project_name, data=None):
title = _("Save project '") + project_name + _("' before exiting?")
content = dialogutils.get_warning_message_dialog_panel(title, msg, False, Gtk.STOCK_QUIT)
dialog = Gtk.Dialog("",
parent_window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Don't Save"), Gtk.ResponseType.CLOSE,
_("Cancel"), Gtk.ResponseType.CANCEL,
_("Save"), Gtk.ResponseType.YES))
alignment = dialogutils.get_default_alignment(content)
dialog.vbox.pack_start(alignment, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
_default_behaviour(dialog)
if data == None:
dialog.connect('response', callback)
else:
dialog.connect('response', callback, data)
dialog.show_all()
def close_confirm_dialog(callback, msg, parent_window, project_name):
title = _("Save project '") + project_name + _("' before closing project?")
content = dialogutils.get_warning_message_dialog_panel(title, msg, False, Gtk.STOCK_QUIT)
align = dialogutils.get_default_alignment(content)
dialog = Gtk.Dialog("",
parent_window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Don't Save"), Gtk.ResponseType.CLOSE,
_("Cancel"), Gtk.ResponseType.CANCEL,
_("Save"), Gtk.ResponseType.YES))
dialog.vbox.pack_start(align, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
_default_behaviour(dialog)
dialog.connect('response', callback)
dialog.show_all()
def about_dialog(parent_window):
dialog = Gtk.Dialog(_("About"), gui.editor_window.window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("OK"), Gtk.ResponseType.ACCEPT))
# Application tab
img = Gtk.Image.new_from_file(respaths.IMAGE_PATH + "flowbladeappicon.png")
flow_label = Gtk.Label(label="Flowblade Movie Editor")
ver_label = Gtk.Label(label="2.4.0")
janne_label = Gtk.Label(label="Copyright 2019 Janne Liljeblad and contributors")
page_label = Gtk.Label(label=_("Project page:") + " " + "https://github.com/jliljebl/flowblade")
page_label.set_use_markup(True)
flow_label.modify_font(Pango.FontDescription("sans bold 14"))
janne_label.modify_font(Pango.FontDescription("sans 10"))
page_label.modify_font(Pango.FontDescription("sans 10"))
vbox = Gtk.VBox(False, 4)
vbox.pack_start(guiutils.get_pad_label(30, 12), False, False, 0)
vbox.pack_start(img, False, False, 0)
vbox.pack_start(guiutils.get_pad_label(30, 4), False, False, 0)
vbox.pack_start(flow_label, False, False, 0)
vbox.pack_start(ver_label, False, False, 0)
vbox.pack_start(guiutils.get_pad_label(30, 12), False, False, 0)
vbox.pack_start(Gtk.Label(), True, True, 0)
vbox.pack_start(janne_label, False, False, 0)
vbox.pack_start(page_label, False, False, 0)
alignment = dialogutils.get_default_alignment(vbox)
alignment.set_size_request(450, 370)
# Thanks tab
up_label = Gtk.Label(label=_("Upstream:"))
up_projs = Gtk.Label(label="MLT")
up_projs2 = Gtk.Label("FFMpeg, Frei0r, LADSPA, Cairo, Gnome, Linux")
tools_label = Gtk.Label(label=_("Tools:"))
tools_list = Gtk.Label("Geany, Inkscape, Gimp, ack-grep")
up_label.modify_font(Pango.FontDescription("sans bold 12"))
tools_label.modify_font(Pango.FontDescription("sans bold 12"))
vbox2 = Gtk.VBox(False, 4)
vbox2.pack_start(guiutils.get_pad_label(30, 12), False, False, 0)
vbox2.pack_start(up_label, False, False, 0)
vbox2.pack_start(up_projs, False, False, 0)
vbox2.pack_start(up_projs2, False, False, 0)
vbox2.pack_start(guiutils.get_pad_label(30, 22), False, False, 0)
vbox2.pack_start(tools_label, False, False, 0)
vbox2.pack_start(tools_list, False, False, 0)
vbox2.pack_start(guiutils.get_pad_label(30, 22), False, False, 0)
vbox2.pack_start(Gtk.Label(), True, True, 0)
alignment2 = dialogutils.get_default_alignment(vbox2)
alignment2.set_size_request(450, 370)
# Licence tab
license_view = guicomponents.get_gpl3_scroll_widget((450, 370))
alignment3 = dialogutils.get_default_alignment(license_view)
alignment3.set_size_request(450, 370)
# Developers tab
lead_label = Gtk.Label(label=_("Lead Developer:"))
lead_label.modify_font(Pango.FontDescription("sans bold 12"))
lead_info = Gtk.Label(label="Janne Liljeblad")
developers_label = Gtk.Label(_("Developers:"))
developers_label.modify_font(Pango.FontDescription("sans bold 12"))
devs_file = open(respaths.DEVELOPERS_DOC)
devs_text = devs_file.read()
devs_info = Gtk.Label(label=devs_text)
contributos_label = Gtk.Label(label=_("Contributors:"))
contributos_label.modify_font(Pango.FontDescription("sans bold 12"))
contributors_file = open(respaths.CONTRIBUTORS_DOC)
contributors_text = contributors_file.read()
contributors_view = Gtk.TextView()
contributors_view.set_editable(False)
contributors_view.set_pixels_above_lines(2)
contributors_view.set_left_margin(2)
contributors_view.set_wrap_mode(Gtk.WrapMode.WORD)
contributors_view.get_buffer().set_text(contributors_text)
contributors_view.set_justification(2) # Centered
guiutils.set_margins(contributors_view, 0, 0, 30, 30)
vbox3 = Gtk.VBox(False, 4)
vbox3.pack_start(guiutils.get_pad_label(30, 12), False, False, 0)
vbox3.pack_start(lead_label, False, False, 0)
vbox3.pack_start(lead_info, False, False, 0)
vbox3.pack_start(guiutils.get_pad_label(30, 22), False, False, 0)
vbox3.pack_start(developers_label, False, False, 0)
vbox3.pack_start(guiutils.get_centered_box([devs_info]), False, False, 0)
vbox3.pack_start(guiutils.get_pad_label(30, 22), False, False, 0)
vbox3.pack_start(contributos_label, False, False, 0)
vbox3.pack_start(contributors_view, False, False, 0)
alignment5 = dialogutils.get_default_alignment(vbox3)
alignment5.set_size_request(450, 370)
# Translations tab
translations_label = Gtk.Label(label=_("Translations by:"))
translations_label.modify_font(Pango.FontDescription("sans bold 12"))
translations_view = guicomponents.get_translations_scroll_widget((450, 370))
vbox4 = Gtk.VBox(False, 4)
vbox4.pack_start(guiutils.get_pad_label(30, 12), False, False, 0)
vbox4.pack_start(translations_label, False, False, 0)
vbox4.pack_start(translations_view, False, False, 0)
alignment4 = dialogutils.get_default_alignment(vbox4)
alignment4.set_size_request(450, 370)
notebook = Gtk.Notebook()
notebook.set_size_request(450 + 10, 370 + 10)
notebook.append_page(alignment, Gtk.Label(label=_("Application")))
notebook.append_page(alignment2, Gtk.Label(label=_("Thanks")))
notebook.append_page(alignment3, Gtk.Label(label=_("License")))
notebook.append_page(alignment5, Gtk.Label(label=_("Developers")))
notebook.append_page(alignment4, Gtk.Label(label=_("Translations")))
guiutils.set_margins(notebook, 6, 6, 6, 0)
dialog.vbox.pack_start(notebook, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
dialog.connect('response', _dialog_destroy)
dialog.show_all()
def environment_dialog(parent_window):
dialog = Gtk.Dialog(_("Runtime Environment"), gui.editor_window.window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("OK"), Gtk.ResponseType.ACCEPT))
COLUMN_WIDTH = 450
r1 = guiutils.get_left_justified_box([Gtk.Label(label=_("MLT version: ")), Gtk.Label(label=str(editorstate.mlt_version))])
try:
major, minor, rev = editorstate.gtk_version
gtk_ver = str(major) + "." + str(minor) + "." + str(rev)
except:
gtk_ver = str(editorstate.gtk_version)
r2 = guiutils.get_left_justified_box([Gtk.Label(label=_("GTK version: ")), Gtk.Label(label=gtk_ver)])
lc, encoding = locale.getdefaultlocale()
r3 = guiutils.get_left_justified_box([Gtk.Label(label=_("Locale: ")), Gtk.Label(label=str(lc))])
if editorstate.app_running_from == editorstate.RUNNING_FROM_INSTALLATION:
run_type = _("INSTALLATION")
elif editorstate.app_running_from == editorstate.RUNNING_FROM_FLATPAK:
run_type = "FLATPAK"
else:
run_type = _("DEVELOPER VERSION")
r4 = guiutils.get_left_justified_box([Gtk.Label(label=_("Running from: ")), Gtk.Label(label=run_type)])
vbox = Gtk.VBox(False, 4)
vbox.pack_start(r1, False, False, 0)
vbox.pack_start(r2, False, False, 0)
vbox.pack_start(r3, False, False, 0)
vbox.pack_start(r4, False, False, 0)
filters = sorted(mltenv.services)
filters_sw = _get_items_in_scroll_window(filters, 7, COLUMN_WIDTH, 140)
transitions = sorted(mltenv.transitions)
transitions_sw = _get_items_in_scroll_window(transitions, 7, COLUMN_WIDTH, 140)
v_codecs = sorted(mltenv.vcodecs)
v_codecs_sw = _get_items_in_scroll_window(v_codecs, 6, COLUMN_WIDTH, 125)
a_codecs = sorted(mltenv.acodecs)
a_codecs_sw = _get_items_in_scroll_window(a_codecs, 6, COLUMN_WIDTH, 125)
formats = sorted(mltenv.formats)
formats_sw = _get_items_in_scroll_window(formats, 5, COLUMN_WIDTH, 105)
enc_ops = renderconsumer.encoding_options + renderconsumer.not_supported_encoding_options
enc_msgs = []
for e_opt in enc_ops:
if e_opt.supported:
msg = e_opt.name + _(" AVAILABLE")
else:
msg = e_opt.name + _(" NOT AVAILABLE, ") + e_opt.err_msg + _(" MISSING")
enc_msgs.append(msg)
enc_opt_sw = _get_items_in_scroll_window(enc_msgs, 5, COLUMN_WIDTH, 115)
missing_mlt_services = []
for f in mltfilters.not_found_filters:
msg = "mlt.Filter " + f.mlt_service_id + _(" FOR FILTER ") + f.name + _(" NOT FOUND")
missing_mlt_services.append(msg)
for t in mlttransitions.not_found_transitions:
msg = "mlt.Transition " + t.mlt_service_id + _(" FOR TRANSITION ") + t.name + _(" NOT FOUND")
missing_services_sw = _get_items_in_scroll_window(missing_mlt_services, 5, COLUMN_WIDTH, 60)
l_pane = Gtk.VBox(False, 4)
l_pane.pack_start(guiutils.get_named_frame(_("General"), vbox), False, False, 0)
l_pane.pack_start(guiutils.get_named_frame(_("MLT Filters"), filters_sw), False, False, 0)
l_pane.pack_start(guiutils.get_named_frame(_("MLT Transitions"), transitions_sw), False, False, 0)
l_pane.pack_start(guiutils.get_named_frame(_("Missing MLT Services"), missing_services_sw), True, True, 0)
r_pane = Gtk.VBox(False, 4)
r_pane.pack_start(guiutils.get_named_frame(_("Video Codecs"), v_codecs_sw), False, False, 0)
r_pane.pack_start(guiutils.get_named_frame(_("Audio Codecs"), a_codecs_sw), False, False, 0)
r_pane.pack_start(guiutils.get_named_frame(_("Formats"), formats_sw), False, False, 0)
r_pane.pack_start(guiutils.get_named_frame(_("Render Options"), enc_opt_sw), False, False, 0)
pane = Gtk.HBox(False, 4)
pane.pack_start(l_pane, False, False, 0)
pane.pack_start(guiutils.pad_label(5, 5), False, False, 0)
pane.pack_start(r_pane, False, False, 0)
a = dialogutils.get_default_alignment(pane)
dialog.vbox.pack_start(a, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
dialog.connect('response', _dialog_destroy)
dialog.show_all()
dialog.set_resizable(False)
def _get_items_in_scroll_window(items, rows_count, w, h):
row_widgets = []
for i in items:
row = guiutils.get_left_justified_box([Gtk.Label(label=i)])
row_widgets.append(row)
items_pane = _get_item_columns_panel(row_widgets, rows_count)
sw = Gtk.ScrolledWindow()
sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
sw.add_with_viewport(items_pane)
sw.set_size_request(w, h)
return sw
def _get_item_columns_panel(items, rows):
hbox = Gtk.HBox(False, 4)
n_item = 0
col_items = 0
vbox = Gtk.VBox()
hbox.pack_start(vbox, False, False, 0)
while n_item < len(items):
item = items[n_item]
vbox.pack_start(item, False, False, 0)
n_item += 1
col_items += 1
if col_items > rows:
vbox = Gtk.VBox()
hbox.pack_start(vbox, False, False, 0)
col_items = 0
return hbox
def file_properties_dialog(data):
dialog = Gtk.Dialog(_("File Properties"), gui.editor_window.window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
( _("OK"), Gtk.ResponseType.ACCEPT))
panel = panels.get_file_properties_panel(data)
alignment = dialogutils.get_default_alignment(panel)
guiutils.set_margins(dialog.vbox, 6, 6, 6, 6)
dialog.vbox.pack_start(alignment, True, True, 0)
_default_behaviour(dialog)
dialog.connect('response', _dialog_destroy)
dialog.show_all()
def clip_properties_dialog(data):
dialog = Gtk.Dialog(_("Clip Properties"), gui.editor_window.window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
( _("OK"), Gtk.ResponseType.ACCEPT))
panel = panels.get_clip_properties_panel(data)
alignment = dialogutils.get_default_alignment(panel)
dialog.vbox.pack_start(alignment, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
_default_behaviour(dialog)
dialog.connect('response', _dialog_destroy)
dialog.show_all()
def _dialog_destroy(dialog, response):
dialog.destroy()
def _default_behaviour(dialog):
dialog.set_default_response(Gtk.ResponseType.OK)
dialog.set_resizable(False)
def load_dialog():
dialog = Gtk.Window(Gtk.WindowType.TOPLEVEL)
dialog.set_title(_("Loading project"))
info_label = Gtk.Label(label="")
status_box = Gtk.HBox(False, 2)
status_box.pack_start(info_label, False, False, 0)
status_box.pack_start(Gtk.Label(), True, True, 0)
progress_bar = Gtk.ProgressBar()
progress_bar.set_fraction(0.2)
progress_bar.set_pulse_step(0.1)
est_box = Gtk.HBox(False, 2)
est_box.pack_start(Gtk.Label(label=""),False, False, 0)
est_box.pack_start(Gtk.Label(), True, True, 0)
progress_vbox = Gtk.VBox(False, 2)
progress_vbox.pack_start(status_box, False, False, 0)
progress_vbox.pack_start(progress_bar, True, True, 0)
progress_vbox.pack_start(est_box, False, False, 0)
alignment = guiutils.set_margins(progress_vbox, 12, 12, 12, 12)
dialog.add(alignment)
dialog.set_default_size(400, 70)
dialog.set_position(Gtk.WindowPosition.CENTER)
dialog.show_all()
# Make refs available for updates
dialog.progress_bar = progress_bar
dialog.info = info_label
return dialog
def recreate_icons_progress_dialog():
return _text_info_prograss_dialog(_("Recreating icons"))
def update_media_lengths_progress_dialog():
return _text_info_prograss_dialog(_("Update media lengths data"))
def audio_sync_active_dialog():
return _text_info_prograss_dialog(_("Comparing Audio Data..."))
def _text_info_prograss_dialog(title):
dialog = Gtk.Window(Gtk.WindowType.TOPLEVEL)
dialog.set_title(title)
info_label = Gtk.Label(label="")
status_box = Gtk.HBox(False, 2)
status_box.pack_start(info_label, False, False, 0)
status_box.pack_start(Gtk.Label(), True, True, 0)
progress_bar = Gtk.ProgressBar()
progress_bar.set_fraction(0.0)
est_box = Gtk.HBox(False, 2)
est_box.pack_start(Gtk.Label(label=""),False, False, 0)
est_box.pack_start(Gtk.Label(), True, True, 0)
progress_vbox = Gtk.VBox(False, 2)
progress_vbox.pack_start(status_box, False, False, 0)
progress_vbox.pack_start(progress_bar, True, True, 0)
progress_vbox.pack_start(est_box, False, False, 0)
alignment = guiutils.set_margins(progress_vbox, 12, 12, 12, 12)
dialog.add(alignment)
dialog.set_default_size(400, 70)
dialog.set_position(Gtk.WindowPosition.CENTER)
dialog.show_all()
dialog.set_keep_above(True) # Perhaps configurable later
# Make refs available for updates
dialog.progress_bar = progress_bar
dialog.info = info_label
return dialog
def proxy_delete_warning_dialog(parent_window, callback):
title = _("Are you sure you want to delete these media files?")
msg1 = _("One or more of the Media Files you are deleting from the project\neither have proxy files or are proxy files.\n\n")
msg2 = _("Deleting these files could prevent converting between\nusing proxy files and using original media.\n\n")
msg = msg1 + msg2
content = dialogutils.get_warning_message_dialog_panel(title, msg)
align = guiutils.set_margins(content, 12, 12, 12, 12)
dialog = Gtk.Dialog("",
parent_window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Cancel"), Gtk.ResponseType.CANCEL,
_("Force Delete"), Gtk.ResponseType.OK))
dialog.vbox.pack_start(align, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
_default_behaviour(dialog)
dialog.set_default_response(Gtk.ResponseType.CANCEL)
dialog.connect('response', callback)
dialog.show_all()
def autosave_recovery_dialog(callback, parent_window):
title = _("Open last autosave?")
msg1 = _("It seems that Flowblade exited abnormally last time.\n\n")
msg2 = _("If there is another instance of Flowblade running,\nthis dialog has probably detected its autosave file.\n\n")
msg3 = _("It is NOT possible to open this autosaved version later.")
msg = msg1 + msg2 + msg3
content = dialogutils.get_warning_message_dialog_panel(title, msg)
align = dialogutils.get_default_alignment(content)
dialog = Gtk.Dialog("",
parent_window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Continue with default 'untitled' project"), Gtk.ResponseType.CANCEL,
_("Open Autosaved Project"), Gtk.ResponseType.OK))
dialog.vbox.pack_start(align, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
dialog.vbox.set_margin_left(6)
_default_behaviour(dialog)
dialog.connect('response', callback)
dialog.show_all()
def autosaves_many_recovery_dialog(response_callback, autosaves, parent_window):
title = _("Open a autosave file?")
msg1 = _("There are multiple autosave files from application crashes.\n\n")
msg3 = _("If you just experienced a crash, select the last created autosave file\nto continue working.\n\n")
msg4 = _("If you see this at application start without a recent crash,\nyou should probably delete all autosave files to stop seeing this dialog.")
msg = msg1 + msg3 + msg4
info_panel = dialogutils.get_warning_message_dialog_panel(title, msg)
autosaves_view = guicomponents.AutoSavesListView()
autosaves_view.set_size_request(300, 300)
autosaves_view.fill_data_model(autosaves)
delete_all = Gtk.Button(_("Delete all autosaves"))
delete_all.connect("clicked", lambda w : _autosaves_delete_all_clicked(autosaves, autosaves_view, dialog))
delete_all_but_selected = Gtk.Button(_("Delete all but selected autosave"))
delete_all_but_selected.connect("clicked", lambda w : _autosaves_delete_unselected(autosaves, autosaves_view))
delete_buttons_vbox = Gtk.HBox()
delete_buttons_vbox.pack_start(Gtk.Label(), True, True, 0)
delete_buttons_vbox.pack_start(delete_all, False, False, 0)
delete_buttons_vbox.pack_start(delete_all_but_selected, False, False, 0)
delete_buttons_vbox.pack_start(Gtk.Label(), True, True, 0)
pane = Gtk.VBox()
pane.pack_start(info_panel, False, False, 0)
pane.pack_start(delete_buttons_vbox, False, False, 0)
pane.pack_start(guiutils.get_pad_label(12,12), False, False, 0)
pane.pack_start(autosaves_view, False, False, 0)
align = dialogutils.get_default_alignment(pane)
dialog = Gtk.Dialog("",
parent_window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Continue with default 'untitled' project"), Gtk.ResponseType.CANCEL,
_("Open Selected Autosave"), Gtk.ResponseType.OK))
dialog.vbox.pack_start(align, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
dialog.vbox.set_margin_left(6)
_default_behaviour(dialog)
dialog.connect('response', response_callback, autosaves_view, autosaves)
dialog.show_all()
def _autosaves_delete_all_clicked(autosaves, autosaves_view, dialog):
for autosave in autosaves:
os.remove(autosave.path)
dialog.set_response_sensitive(Gtk.ResponseType.OK, False)
del autosaves[:]
autosaves_view.fill_data_model(autosaves)
def _autosaves_delete_unselected(autosaves, autosaves_view):
selected_autosave = autosaves.pop(autosaves_view.get_selected_indexes_list()[0])
for autosave in autosaves:
os.remove(autosave.path)
del autosaves[:]
autosaves.append(selected_autosave)
autosaves_view.fill_data_model(autosaves)
def tracks_count_change_dialog(callback, v_tracks, a_tracks):
dialog = Gtk.Dialog(_("Change Sequence Tracks Count"), gui.editor_window.window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Cancel"), Gtk.ResponseType.REJECT,
_("Change Tracks"), Gtk.ResponseType.ACCEPT))
tracks_select = guicomponents.TracksNumbersSelect(v_tracks, a_tracks)
info_text = _("Please note:\n\n") + \
"\u2022" + _(" When reducing the number of tracks the top Video track and/or bottom Audio track will be removed\n") + \
"\u2022" + _(" It is recommended that you save Project before completing this operation\n") + \
"\u2022" + _(" There is no Undo for this operation\n") + \
"\u2022" + _(" Current Undo Stack will be destroyed\n") + \
"\u2022" + _(" All Clips and Compositors on deleted Tracks will be permanently destroyed")
info_label = Gtk.Label(label=info_text)
info_label.set_use_markup(True)
info_box = guiutils.get_left_justified_box([info_label])
pad = guiutils.get_pad_label(24, 24)
tracks_vbox = Gtk.VBox(False, 2)
tracks_vbox.pack_start(info_box, False, False, 0)
tracks_vbox.pack_start(pad, False, False, 0)
tracks_vbox.pack_start(tracks_select.widget, False, False, 0)
alignment = dialogutils.get_alignment2(tracks_vbox)
dialog.vbox.pack_start(alignment, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
_default_behaviour(dialog)
dialog.connect('response', callback, tracks_select)
dialog.show_all()
def clip_length_change_dialog(callback, clip, track):
dialog = Gtk.Dialog(_("Change Clip Length"), gui.editor_window.window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Cancel"), Gtk.ResponseType.REJECT,
_("Ok"), Gtk.ResponseType.ACCEPT))
length_changer = guicomponents.ClipLengthChanger(clip)
vbox = Gtk.VBox(False, 2)
vbox.pack_start(length_changer.widget, False, False, 0)
vbox.pack_start(guiutils.get_pad_label(24, 24), False, False, 0)
alignment = dialogutils.get_alignment2(vbox)
dialog.vbox.pack_start(alignment, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
_default_behaviour(dialog)
dialog.connect('response', callback, clip, track, length_changer)
dialog.show_all()
def new_sequence_dialog(callback, default_name):
dialog = Gtk.Dialog(_("Create New Sequence"), gui.editor_window.window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Cancel"), Gtk.ResponseType.REJECT,
_("Create Sequence"), Gtk.ResponseType.ACCEPT))
name_entry = Gtk.Entry()
name_entry.set_width_chars(30)
name_entry.set_text(default_name)
name_entry.set_activates_default(True)
name_select = panels.get_two_column_box(Gtk.Label(label=_("Sequence Name:")),
name_entry,
250)
tracks_select = guicomponents.TracksNumbersSelect(appconsts.INIT_V_TRACKS, appconsts.INIT_A_TRACKS)
open_check = Gtk.CheckButton()
open_check.set_active(True)
open_label = Gtk.Label(label=_("Open For Editing:"))
open_hbox = Gtk.HBox(False, 2)
open_hbox.pack_start(Gtk.Label(), True, True, 0)
open_hbox.pack_start(open_label, False, False, 0)
open_hbox.pack_start(open_check, False, False, 0)
tracks_vbox = Gtk.VBox(False, 2)
tracks_vbox.pack_start(name_select, False, False, 0)
tracks_vbox.pack_start(guiutils.get_pad_label(12, 2), False, False, 0)
tracks_vbox.pack_start(tracks_select.widget, False, False, 0)
tracks_vbox.pack_start(guiutils.get_pad_label(12, 12), False, False, 0)
tracks_vbox.pack_start(open_hbox, False, False, 0)
alignment = dialogutils.get_alignment2(tracks_vbox)
dialog.vbox.pack_start(alignment, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
_default_behaviour(dialog)
dialog.connect('response', callback, (name_entry, tracks_select, open_check))
dialog.show_all()
def new_media_name_dialog(callback, media_file):
dialog = Gtk.Dialog(_("Rename New Media Object"), gui.editor_window.window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Cancel"), Gtk.ResponseType.REJECT,
_("Rename"), Gtk.ResponseType.ACCEPT))
name_entry = Gtk.Entry()
name_entry.set_width_chars(30)
name_entry.set_text(media_file.name)
name_entry.set_activates_default(True)
name_select = panels.get_two_column_box(Gtk.Label(label=_("New Name:")),
name_entry,
180)
tracks_vbox = Gtk.VBox(False, 2)
tracks_vbox.pack_start(name_select, False, False, 0)
tracks_vbox.pack_start(guiutils.get_pad_label(12, 12), False, False, 0)
alignment = dialogutils.get_alignment2(tracks_vbox)
dialog.vbox.pack_start(alignment, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
_default_behaviour(dialog)
dialog.set_default_response(Gtk.ResponseType.ACCEPT)
dialog.connect('response', callback, (name_entry, media_file))
dialog.show_all()
def new_clip_name_dialog(callback, clip):
dialog = Gtk.Dialog(_("Rename Clip"), gui.editor_window.window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Cancel"), Gtk.ResponseType.REJECT,
_("Rename"), Gtk.ResponseType.ACCEPT))
name_entry = Gtk.Entry()
name_entry.set_width_chars(30)
name_entry.set_text(clip.name)
name_entry.set_activates_default(True)
name_select = panels.get_two_column_box(Gtk.Label(label=_("New Name:")),
name_entry,
180)
tracks_vbox = Gtk.VBox(False, 2)
tracks_vbox.pack_start(name_select, False, False, 0)
tracks_vbox.pack_start(guiutils.get_pad_label(12, 12), False, False, 0)
alignment = dialogutils.get_alignment2(tracks_vbox)
dialog.vbox.pack_start(alignment, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
_default_behaviour(dialog)
dialog.set_default_response(Gtk.ResponseType.ACCEPT)
dialog.connect('response', callback, (name_entry, clip))
dialog.show_all()
def new_media_log_group_name_dialog(callback, next_index, add_selected):
dialog = Gtk.Dialog(_("New Range Item Group"), gui.editor_window.window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Cancel"), Gtk.ResponseType.REJECT,
_("Create"), Gtk.ResponseType.OK))
name_entry = Gtk.Entry()
name_entry.set_width_chars(30)
name_entry.set_text(_("User Group ") + str(next_index))
name_entry.set_activates_default(True)
name_select = panels.get_two_column_box(Gtk.Label(label=_("New Group Name:")),
name_entry,
180)
vbox = Gtk.VBox(False, 2)
vbox.pack_start(name_select, False, False, 0)
alignment = dialogutils.get_default_alignment(vbox)
dialog.vbox.pack_start(alignment, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
_default_behaviour(dialog)
dialog.set_default_response(Gtk.ResponseType.ACCEPT)
dialog.connect('response', callback, (name_entry, add_selected))
dialog.show_all()
def group_rename_dialog(callback, group_name):
dialog, entry = dialogutils.get_single_line_text_input_dialog(30, 130,
_("Rename Range Log Item Group"),
_("Rename"),
_("New Group Name:"),
group_name)
dialog.connect('response', callback, entry)
dialog.show_all()
def not_valid_producer_dialog(file_path, parent_window):
primary_txt = _("Can't open non-valid media")
secondary_txt = _("File: ") + file_path + _("\nis not a valid media file.")
dialogutils.warning_message(primary_txt, secondary_txt, parent_window, is_info=True)
def marker_name_dialog(frame_str, callback):
dialog = Gtk.Dialog(_("New Marker"), gui.editor_window.window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Add Marker"), Gtk.ResponseType.ACCEPT))
name_entry = Gtk.Entry()
name_entry.set_width_chars(30)
name_entry.set_text("")
name_entry.set_activates_default(True)
name_select = panels.get_two_column_box(Gtk.Label(label=_("Name for marker at ") + frame_str),
name_entry,
250)
alignment = dialogutils.get_default_alignment(name_select)
dialog.vbox.pack_start(alignment, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
dialog.set_default_response(Gtk.ResponseType.ACCEPT)
_default_behaviour(dialog)
dialog.connect('response', callback, name_entry)
dialog.show_all()
def clip_marker_name_dialog(clip_frame_str, tline_frame_str, callback, data):
dialog = Gtk.Dialog(_("New Marker"), gui.editor_window.window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Add Marker"), Gtk.ResponseType.ACCEPT))
tline_frame_info = guiutils.get_left_justified_box([Gtk.Label(_("Timeline position: ") + tline_frame_str),Gtk.Label()])
name_entry = Gtk.Entry()
name_entry.set_width_chars(30)
name_entry.set_text("")
name_entry.set_activates_default(True)
name_select = panels.get_two_column_box(Gtk.Label(label=_("Name for clip marker at ") + clip_frame_str),
name_entry,
250)
rows_vbox = Gtk.VBox(False, 2)
rows_vbox.pack_start(tline_frame_info, False, False, 0)
rows_vbox.pack_start(name_select, False, False, 0)
#rows_vbox.pack_start(guiutils.get_pad_label(12, 2), False, False, 0)
alignment = dialogutils.get_default_alignment(rows_vbox)
dialog.vbox.pack_start(alignment, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
dialog.set_default_response(Gtk.ResponseType.ACCEPT)
_default_behaviour(dialog)
dialog.connect('response', callback, name_entry, data)
dialog.show_all()
def alpha_info_msg(callback, filter_name):
dialog = Gtk.Dialog(_("Alpha Filters Info"), gui.editor_window.window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Ok"), Gtk.ResponseType.ACCEPT))
line_label = Gtk.Label(_("You are adding Alpha Filter '") + filter_name + _("' into a clip. Here is some info on how Alpha Filters work on Flowblade:"))
line_label.set_use_markup(True)
row1 = guiutils.get_left_justified_box([line_label])
info_text = "\u2022" + _(" Alpha Filters work by modifying image's alpha channel.\n") + \
"\u2022" + _(" To see the effect of Alpha Filter you need composite this clip on track below by adding a Compositor like 'Dissolve' into this clip.\n") + \
"\u2022" + _(" Alpha Filters on clips on Track V1 have no effect.")
info_label = Gtk.Label(label=info_text)
info_label.set_use_markup(True)
info_box = guiutils.get_left_justified_box([info_label])
dont_show_check = Gtk.CheckButton.new_with_label (_("Don't show this message again."))
row2 = guiutils.get_left_justified_box([dont_show_check])
vbox = Gtk.VBox(False, 2)
vbox.pack_start(row1, False, False, 0)
vbox.pack_start(guiutils.pad_label(24, 12), False, False, 0)
vbox.pack_start(info_box, False, False, 0)
vbox.pack_start(guiutils.pad_label(24, 24), False, False, 0)
vbox.pack_start(row2, False, False, 0)
alignment = dialogutils.get_default_alignment(vbox)
dialog.vbox.pack_start(alignment, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
dialog.set_default_response(Gtk.ResponseType.ACCEPT)
_default_behaviour(dialog)
dialog.connect('response', callback, dont_show_check)
dialog.show_all()
def open_image_sequence_dialog(callback, parent_window):
cancel_str = _("Cancel")
ok_str = _("Ok")
dialog = Gtk.Dialog(_("Add Image Sequence Clip"),
parent_window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(cancel_str, Gtk.ResponseType.CANCEL,
ok_str, Gtk.ResponseType.YES))
file_chooser = Gtk.FileChooserButton(_("Select First Frame"))
file_chooser.set_size_request(250, 25)
if ((editorpersistance.prefs.open_in_last_opended_media_dir == True)
and (editorpersistance.prefs.last_opened_media_dir != None)):
file_chooser.set_current_folder(editorpersistance.prefs.last_opened_media_dir)
else:
file_chooser.set_current_folder(os.path.expanduser("~") + "/")
filt = utils.get_image_sequence_file_filter()
file_chooser.add_filter(filt)
row1 = guiutils.get_two_column_box(Gtk.Label(label=_("First frame:")), file_chooser, 220)
adj = Gtk.Adjustment(value=1, lower=1, upper=250, step_incr=1)
frames_per_image = Gtk.SpinButton(adjustment=adj, climb_rate=1.0, digits=0)
row2 = guiutils.get_two_column_box(Gtk.Label(label=_("Frames per Source Image:")), frames_per_image, 220)
vbox = Gtk.VBox(False, 2)
vbox.pack_start(row1, False, False, 0)
vbox.pack_start(row2, False, False, 0)
alignment = dialogutils.get_alignment2(vbox)
dialog.vbox.pack_start(alignment, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
_default_behaviour(dialog)
dialog.connect('response', callback, (file_chooser, frames_per_image))
dialog.show_all()
def export_edl_dialog(callback, parent_window, project_name):
dialog = Gtk.FileChooserDialog(_("Export EDL"), parent_window,
Gtk.FileChooserAction.SAVE,
(_("Cancel"), Gtk.ResponseType.CANCEL,
_("Export"), Gtk.ResponseType.ACCEPT))
dialog.set_action(Gtk.FileChooserAction.SAVE)
project_name = project_name.rstrip(".flb")
dialog.set_current_name(project_name + ".edl")
dialog.set_do_overwrite_confirmation(True)
dialog.set_select_multiple(False)
dialog.connect('response', callback)
dialog.show()
def transition_edit_dialog(callback, transition_data):
dialog = Gtk.Dialog(_("Add Transition"), gui.editor_window.window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Cancel"), Gtk.ResponseType.REJECT,
_("Apply"), Gtk.ResponseType.ACCEPT))
alignment, type_combo, length_entry, encodings_cb, quality_cb, wipe_luma_combo_box, color_button = panels.get_transition_panel(transition_data)
widgets = (type_combo, length_entry, encodings_cb, quality_cb, wipe_luma_combo_box, color_button)
dialog.connect('response', callback, widgets, transition_data)
dialog.vbox.pack_start(alignment, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
_default_behaviour(dialog)
dialog.show_all()
def transition_re_render_dialog(callback, transition_data):
dialog = Gtk.Dialog(_("Rerender Transition"), gui.editor_window.window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Cancel"), Gtk.ResponseType.REJECT,
_("Rerender"), Gtk.ResponseType.ACCEPT))
alignment, encodings_cb, quality_cb = panels.get_transition_re_render_panel(transition_data)
widgets = (encodings_cb, quality_cb)
dialog.connect('response', callback, widgets, transition_data)
dialog.vbox.pack_start(alignment, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
_default_behaviour(dialog)
dialog.show_all()
def fade_re_render_dialog(callback, fade_data):
dialog = Gtk.Dialog(_("Rerender Fade"), gui.editor_window.window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Cancel"), Gtk.ResponseType.REJECT,
_("Rerender"), Gtk.ResponseType.ACCEPT))
alignment, encodings_cb, quality_cb = panels.get_fade_re_render_panel(fade_data)
widgets = (encodings_cb, quality_cb)
dialog.connect('response', callback, widgets, fade_data)
dialog.vbox.pack_start(alignment, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
_default_behaviour(dialog)
dialog.show_all()
def re_render_all_dialog(callback, rerender_list, unrenderable):
dialog = Gtk.Dialog(_("Rerender All Transitions and Fades"), gui.editor_window.window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Cancel"), Gtk.ResponseType.REJECT,
_("Rerender All"), Gtk.ResponseType.ACCEPT))
alignment, encodings_cb, quality_cb = panels.get_re_render_all_panel(rerender_list, unrenderable)
widgets = (encodings_cb, quality_cb)
dialog.connect('response', callback, widgets, rerender_list)
dialog.vbox.pack_start(alignment, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
_default_behaviour(dialog)
dialog.show_all()
def fade_edit_dialog(callback, transition_data):
dialog = Gtk.Dialog(_("Add Fade"), gui.editor_window.window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Cancel"), Gtk.ResponseType.REJECT,
_("Apply"), Gtk.ResponseType.ACCEPT))
alignment, type_combo, length_entry, encodings_cb, quality_cb, color_button = panels.get_fade_panel(transition_data)
widgets = (type_combo, length_entry, encodings_cb, quality_cb, color_button)
dialog.connect('response', callback, widgets, transition_data)
dialog.vbox.pack_start(alignment, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
_default_behaviour(dialog)
dialog.show_all()
def keyboard_shortcuts_dialog(parent_window, get_tool_list_func, callback):
dialog = Gtk.Dialog(_("Keyboard Shortcuts"),
parent_window,
Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Cancel"), Gtk.ResponseType.REJECT,
_("Apply"), Gtk.ResponseType.ACCEPT))
presets_label = guiutils.bold_label(_("Shortcuts Presets:"))
shortcuts_combo = guicomponents.get_shorcuts_selector()
hbox = Gtk.HBox()
hbox.pack_start(presets_label, False, True, 0)
hbox.pack_start(shortcuts_combo, True, True, 0)
scroll_hold_panel = Gtk.HBox()
diff_label = guiutils.bold_label(_("Diffence to 'Flowblade Default' Presets:"))
diff_data = Gtk.Label()
diff_data.set_line_wrap(True)
diff_data.set_size_request(418, 58)
diff_data.set_text(shortcuts.get_diff_to_defaults(editorpersistance.prefs.shortcuts))
diff_panel = Gtk.VBox()
diff_panel.pack_start(diff_data, False, False, 0)
diff_sw = Gtk.ScrolledWindow()
diff_sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
diff_sw.add_with_viewport(diff_panel)
diff_sw.set_size_request(420, 60)
content_panel = Gtk.VBox(False, 2)
content_panel.pack_start(hbox, False, False, 0)
content_panel.pack_start(guiutils.pad_label(12,12), False, False, 0)
content_panel.pack_start(scroll_hold_panel, True, True, 0)
content_panel.pack_start(guiutils.pad_label(12,12), False, False, 0)
content_panel.pack_start(guiutils.get_left_justified_box([diff_label]), False, False, 0)
content_panel.pack_start(diff_sw, False, False, 0)
scroll_window = _display_keyboard_schortcuts(editorpersistance.prefs.shortcuts, get_tool_list_func(), scroll_hold_panel)
shortcuts_combo.connect('changed', lambda w:_shorcuts_selection_changed(w, scroll_hold_panel, diff_data, dialog))
guiutils.set_margins(content_panel, 12, 12, 12, 12)
dialog.vbox.pack_start(content_panel, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
_default_behaviour(dialog)
dialog.connect('response', callback, shortcuts_combo)
dialog.show_all()
def _shorcuts_selection_changed(combo, scroll_hold_panel, diff_data, dialog):
selected_xml = shortcuts.shortcut_files[combo.get_active()]
_display_keyboard_schortcuts(selected_xml, workflow.get_tline_tool_working_set(), scroll_hold_panel)
diff_data.set_text(shortcuts.get_diff_to_defaults(selected_xml))
dialog.show_all()
def _display_keyboard_schortcuts(xml_file, tool_set, scroll_hold_panel):
widgets = scroll_hold_panel.get_children()
if len(widgets) != 0:
scroll_hold_panel.remove(widgets[0])
shorcuts_panel = _get_dynamic_kb_shortcuts_panel(xml_file, tool_set)
pad_panel = Gtk.HBox()
pad_panel.pack_start(guiutils.pad_label(12,12), False, False, 0)
pad_panel.pack_start(shorcuts_panel, True, False, 0)
pad_panel.pack_start(guiutils.pad_label(12,12), False, False, 0)
sw = Gtk.ScrolledWindow()
sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
sw.add_with_viewport(pad_panel)
sw.set_size_request(420, 400)
scroll_hold_panel.pack_start(sw, False, False, 0)
return sw
def _get_dynamic_kb_shortcuts_panel(xml_file, tool_set):
root_node = shortcuts.get_shortcuts_xml_root_node(xml_file)
general_vbox = Gtk.VBox()
general_vbox.pack_start(_get_kb_row(_("Control + N"), _("Create New Project")), False, False, 0)
general_vbox.pack_start(_get_kb_row(_("Control + S"), _("Save Project")), False, False, 0)
general_vbox.pack_start(_get_dynamic_kb_row(root_node, "delete"), False, False, 0)
general_vbox.pack_start(_get_kb_row(_("ESCAPE"), _("Stop Rendering Audio Levels")), False, False, 0)
general_vbox.pack_start(_get_kb_row(_("Control + Q"), _("Quit")), False, False, 0)
general_vbox.pack_start(_get_kb_row(_("Control + Z"), _("Undo")), False, False, 0)
general_vbox.pack_start(_get_kb_row(_("Control + Y"), _("Redo")), False, False, 0)
general_vbox.pack_start(_get_kb_row(_("Control + O"), _("Open Project")), False, False, 0)
general_vbox.pack_start(_get_dynamic_kb_row(root_node, "switch_monitor"), False, False, 0)
general_vbox.pack_start(_get_dynamic_kb_row(root_node, "open_next"), False, False, 0)
general_vbox.pack_start(_get_kb_row(_("Control + L"), _("Log Marked Clip Range")), False, False, 0)
general_vbox.pack_start(_get_dynamic_kb_row(root_node, "zoom_in"), False, False, 0)
general_vbox.pack_start(_get_dynamic_kb_row(root_node, "zoom_out"), False, False, 0)
general = guiutils.get_named_frame(_("General"), general_vbox)
tline_vbox = Gtk.VBox()
tline_vbox.pack_start(_get_dynamic_kb_row(root_node, "mark_in"), False, False, 0)
tline_vbox.pack_start(_get_dynamic_kb_row(root_node, "mark_out"), False, False, 0)
tline_vbox.pack_start(_get_kb_row(_("Alt + I"), _("Go To Mark In")), False, False, 0)
tline_vbox.pack_start(_get_kb_row(_("Alt + O"), _("Go To Mark Out")), False, False, 0)
tline_vbox.pack_start(_get_dynamic_kb_row(root_node, "cut"), False, False, 0)
tline_vbox.pack_start(_get_dynamic_kb_row(root_node, "cut_all"), False, False, 0)
tline_vbox.pack_start(_get_kb_row(_("DELETE"), _("Splice Out")), False, False, 0)
tline_vbox.pack_start(_get_kb_row(_("Control + DELETE"), _("Lift")), False, False, 0)
tline_vbox.pack_start(_get_dynamic_kb_row(root_node, "insert"), False, False, 0)
tline_vbox.pack_start(_get_dynamic_kb_row(root_node, "append"), False, False, 0)
tline_vbox.pack_start(_get_dynamic_kb_row(root_node, "3_point_overwrite"), False, False, 0)
tline_vbox.pack_start(_get_dynamic_kb_row(root_node, "overwrite_range"), False, False, 0)
tline_vbox.pack_start(_get_dynamic_kb_row(root_node, "append_from_bin"), False, False, 0)
tline_vbox.pack_start(_get_dynamic_kb_row(root_node, "add_marker"), False, False, 0)
tline_vbox.pack_start(_get_kb_row(_("Control + C"), _("Copy Clips")), False, False, 0)
tline_vbox.pack_start(_get_kb_row(_("Control + V"), _("Paste Clips")), False, False, 0)
tline_vbox.pack_start(_get_dynamic_kb_row(root_node, "toggle_ripple"), False, False, 0)
tline_vbox.pack_start(_get_dynamic_kb_row(root_node, "resync"), False, False, 0)
tline_vbox.pack_start(_get_dynamic_kb_row(root_node, "log_range"), False, False, 0)
tline_vbox.pack_start(_get_kb_row(_("Left Arrow "), _("Prev Frame Trim Edit")), False, False, 0)
tline_vbox.pack_start(_get_kb_row(_("Right Arrow"), _("Next Frame Trim Edit")), False, False, 0)
tline_vbox.pack_start(_get_kb_row(_("Control + Left Arrow "), _("Back 10 Frames Trim Edit")), False, False, 0)
tline_vbox.pack_start(_get_kb_row(_("Control + Right Arrow"), _("Forward 10 Frames Trim Edit")), False, False, 0)
tline_vbox.pack_start(_get_dynamic_kb_row(root_node, "enter_edit"), False, False, 0)
tline_vbox.pack_start(_get_dynamic_kb_row(root_node, "nudge_back"), False, False, 0)
tline_vbox.pack_start(_get_dynamic_kb_row(root_node, "nudge_forward"), False, False, 0)
tline_vbox.pack_start(_get_dynamic_kb_row(root_node, "nudge_back_10"), False, False, 0)
tline_vbox.pack_start(_get_dynamic_kb_row(root_node, "nudge_forward_10"), False, False, 0)
tline = guiutils.get_named_frame(_("Timeline"), tline_vbox)
track_head_vbox = Gtk.VBox()
track_head_vbox.pack_start(_get_kb_row(_("Mouse Double Click"), _("Toggle Track Height")), False, False, 0)
track_head = guiutils.get_named_frame(_("Track Head Column"), track_head_vbox)
play_vbox = Gtk.VBox()
play_vbox.pack_start(_get_dynamic_kb_row(root_node, "play_pause"), False, False, 0)
play_vbox.pack_start(_get_dynamic_kb_row(root_node, "slower"), False, False, 0)
play_vbox.pack_start(_get_dynamic_kb_row(root_node, "stop"), False, False, 0)
play_vbox.pack_start(_get_dynamic_kb_row(root_node, "faster"), False, False, 0)
play_vbox.pack_start(_get_dynamic_kb_row(root_node, "prev_frame"), False, False, 0)
play_vbox.pack_start(_get_dynamic_kb_row(root_node, "next_frame"), False, False, 0)
play_vbox.pack_start(_get_kb_row(_("Control + Left Arrow "), _("Move Back 10 Frames")), False, False, 0)
play_vbox.pack_start(_get_kb_row(_("Control + Right Arrow"), _("Move Forward 10 Frames")), False, False, 0)
play_vbox.pack_start(_get_dynamic_kb_row(root_node, "prev_cut"), False, False, 0)
play_vbox.pack_start(_get_dynamic_kb_row(root_node, "next_cut"), False, False, 0)
play_vbox.pack_start(_get_dynamic_kb_row(root_node, "to_start"), False, False, 0)
play_vbox.pack_start(_get_dynamic_kb_row(root_node, "to_end"), False, False, 0)
play_vbox.pack_start(_get_kb_row(_("Shift + I"), _("To Mark In")), False, False, 0)
play_vbox.pack_start(_get_kb_row(_("Shift + O"), _("To Mark Out")), False, False, 0)
play = guiutils.get_named_frame(_("Playback"), play_vbox)
tools_vbox = Gtk.VBox()
for tool_name, kb_shortcut in tool_set:
tools_vbox.pack_start(_get_kb_row(tool_name, kb_shortcut), False, False, 0)
tools_vbox.pack_start(_get_kb_row(_("Keypad 1-9"), _("Same as 1-9")), False, False, 0)
tools = guiutils.get_named_frame(_("Tools"), tools_vbox)
kfs_vbox = Gtk.VBox()
kfs_vbox.pack_start(_get_kb_row(_("Control + C"), _("Copy Keyframe Value")), False, False, 0)
kfs_vbox.pack_start(_get_kb_row(_("Control + V"), _("Paste Keyframe Value")), False, False, 0)
kfs = guiutils.get_named_frame(_("Keyframe and Geometry Editor"), kfs_vbox)
geom_vbox = Gtk.VBox()
geom_vbox.pack_start(_get_kb_row(_("Left Arrow "), _("Move Source Video Left 1px")), False, False, 0)
geom_vbox.pack_start(_get_kb_row(_("Right Arrow"), _("Move Source Video Right 1px")), False, False, 0)
geom_vbox.pack_start(_get_kb_row(_("Up Arrow"), _("Move Source Video Up 1px")), False, False, 0)
geom_vbox.pack_start(_get_kb_row(_("Down Arrow"), _("Move Source Video Down 1px")), False, False, 0)
geom_vbox.pack_start(_get_kb_row(_("Control + Arrow"), _("Move Source Video 10px")), False, False, 0)
geom_vbox.pack_start(_get_kb_row(_("Control + Mouse Drag"), _("Keep Aspect Ratio in Affine Blend scaling")), False, False, 0)
geom_vbox.pack_start(_get_kb_row(_("Shift + Left Arrow "), _("Scale Down")), False, False, 0)
geom_vbox.pack_start(_get_kb_row(_("Shift + Right Arrow"), _("Scale Up")), False, False, 0)
geom_vbox.pack_start(_get_kb_row(_("Shift + Control + Left Arrow "), _("Scale Down More")), False, False, 0)
geom_vbox.pack_start(_get_kb_row(_("Shift + Control + Right Arrow"), _("Scale Up More")), False, False, 0)
geom_vbox.pack_start(_get_kb_row(_("Shift"), _("Snap to X or Y of drag start point")), False, False, 0)
geom = guiutils.get_named_frame(_("Geometry Editor"), geom_vbox)
roto_vbox = Gtk.VBox()
roto_vbox.pack_start(_get_kb_row(_("Delete"), _("Deletes Selected Handle")), False, False, 0)
roto_vbox.pack_start(_get_kb_row(_("Left Arrow "), _("Previous Frame")), False, False, 0)
roto_vbox.pack_start(_get_kb_row(_("Right Arrow"), _("Next Frame")), False, False, 0)
roto = guiutils.get_named_frame(_("RotoMask Editor"), roto_vbox)
panel = Gtk.VBox()
panel.pack_start(tools, False, False, 0)
panel.pack_start(guiutils.pad_label(12,12), False, False, 0)
panel.pack_start(tline, False, False, 0)
panel.pack_start(guiutils.pad_label(12,12), False, False, 0)
panel.pack_start(track_head, False, False, 0)
panel.pack_start(guiutils.pad_label(12,12), False, False, 0)
panel.pack_start(play, False, False, 0)
panel.pack_start(guiutils.pad_label(12,12), False, False, 0)
panel.pack_start(general, False, False, 0)
panel.pack_start(guiutils.pad_label(12,12), False, False, 0)
panel.pack_start(kfs, False, False, 0)
panel.pack_start(guiutils.pad_label(12,12), False, False, 0)
panel.pack_start(geom, False, False, 0)
panel.pack_start(guiutils.pad_label(12,12), False, False, 0)
panel.pack_start(roto, False, False, 0)
return panel
def _get_dynamic_kb_row(root_node, code):
key_name, action_name = shortcuts.get_shortcut_info(root_node, code)
return _get_kb_row(key_name, action_name)
def _get_kb_row(msg1, msg2):
label1 = Gtk.Label(label=msg1)
label2 = Gtk.Label(label=msg2)
KB_SHORTCUT_ROW_WIDTH = 400
KB_SHORTCUT_ROW_HEIGHT = 22
row = guiutils.get_two_column_box(label1, label2, 170)
row.set_size_request(KB_SHORTCUT_ROW_WIDTH, KB_SHORTCUT_ROW_HEIGHT)
row.show()
return row
def watermark_dialog(add_callback, remove_callback):
dialog = Gtk.Dialog(_("Sequence Watermark"), gui.editor_window.window,
Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Close"), Gtk.ResponseType.CLOSE))
seq_label = guiutils.bold_label(_("Sequence:") + " ")
seq_name = Gtk.Label(label=editorstate.current_sequence().name)
file_path_label = guiutils.bold_label(_("Watermark:") + " ")
add_button = Gtk.Button(_("Set Watermark File"))
remove_button = Gtk.Button(_("Remove Watermark"))
if editorstate.current_sequence().watermark_file_path == None:
file_path_value_label = Gtk.Label(label=_("Not Set"))
add_button.set_sensitive(True)
remove_button.set_sensitive(False)
else:
file_path_value_label = Gtk.Label(label=editorstate.current_sequence().watermark_file_path)
add_button.set_sensitive(False)
remove_button.set_sensitive(True)
row1 = guiutils.get_left_justified_box([seq_label, seq_name])
row2 = guiutils.get_left_justified_box([file_path_label, file_path_value_label])
row3 = guiutils.get_left_justified_box([Gtk.Label(), remove_button, guiutils.pad_label(8, 8), add_button])
row3.set_size_request(470, 30)
widgets = (add_button, remove_button, file_path_value_label)
add_button.connect("clicked", add_callback, dialog, widgets)
remove_button.connect("clicked", remove_callback, widgets)
vbox = Gtk.VBox(False, 2)
vbox.pack_start(row1, False, False, 0)
vbox.pack_start(row2, False, False, 0)
vbox.pack_start(guiutils.pad_label(12, 8), False, False, 0)
vbox.pack_start(row3, False, False, 0)
alignment = dialogutils.get_default_alignment(vbox)
#alignment.set_padding(12, 12, 12, 12)
#alignment.add(vbox)
dialog.vbox.pack_start(alignment, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
_default_behaviour(dialog)
dialog.connect('response', _dialog_destroy)
dialog.show_all()
def watermark_file_dialog(callback, parent, widgets):
dialog = Gtk.FileChooserDialog(_("Select Watermark File"), None,
Gtk.FileChooserAction.OPEN,
(_("Cancel"), Gtk.ResponseType.CANCEL,
_("OK"), Gtk.ResponseType.ACCEPT))
dialog.set_action(Gtk.FileChooserAction.OPEN)
dialog.set_select_multiple(False)
file_filter = Gtk.FileFilter()
file_filter.set_name("Accepted Watermark Files")
file_filter.add_pattern("*" + ".png")
file_filter.add_pattern("*" + ".jpeg")
file_filter.add_pattern("*" + ".jpg")
file_filter.add_pattern("*" + ".tga")
dialog.add_filter(file_filter)
dialog.connect('response', callback, widgets)
dialog.show()
def media_file_dialog(text, callback, multiple_select, data=None, parent=None, open_dir=None):
if parent == None:
parent = gui.editor_window.window
file_select = Gtk.FileChooserDialog(text, parent, Gtk.FileChooserAction.OPEN,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
file_select.set_default_response(Gtk.ResponseType.CANCEL)
file_select.set_select_multiple(multiple_select)
media_filter = utils.get_media_source_file_filter()
all_filter = Gtk.FileFilter()
all_filter.set_name(_("All files"))
all_filter.add_pattern("*.*")
file_select.add_filter(media_filter)
file_select.add_filter(all_filter)
if ((editorpersistance.prefs.open_in_last_opended_media_dir == True)
and (editorpersistance.prefs.last_opened_media_dir != None)):
file_select.set_current_folder(editorpersistance.prefs.last_opened_media_dir)
if open_dir != None:
file_select.set_current_folder(open_dir)
if data == None:
file_select.connect('response', callback)
else:
file_select.connect('response', callback, data)
file_select.set_modal(True)
file_select.show()
def save_snaphot_progess(media_copy_txt, project_txt):
dialog = Gtk.Window(Gtk.WindowType.TOPLEVEL)
dialog.set_title(_("Saving project snapshot"))
dialog.media_copy_info = Gtk.Label(label=media_copy_txt)
media_copy_row = guiutils.get_left_justified_box([dialog.media_copy_info])
dialog.saving_project_info = Gtk.Label(label=project_txt)
project_row = guiutils.get_left_justified_box([dialog.saving_project_info])
progress_vbox = Gtk.VBox(False, 2)
progress_vbox.pack_start(media_copy_row, False, False, 0)
progress_vbox.pack_start(project_row, True, True, 0)
alignment = guiutils.set_margins(progress_vbox, 12, 12, 12, 12)
dialog.add(alignment)
dialog.set_default_size(400, 70)
dialog.set_position(Gtk.WindowPosition.CENTER)
dialog.show_all()
return dialog
def not_matching_media_info_dialog(project, media_file, callback):
dialog = Gtk.Dialog(_("Loaded Media Profile Mismatch"), gui.editor_window.window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Keep Current Profile"), Gtk.ResponseType.REJECT,
_("Change To File Profile"), Gtk.ResponseType.ACCEPT))
primary_txt = _("A video file was loaded that does not match the Project Profile!")
secondary_txt = ""
match_profile_index = mltprofiles.get_closest_matching_profile_index(media_file.info)
match_profile_name = mltprofiles.get_profile_name_for_index(match_profile_index)
project_profile_name = project.profile.description()
row1 = guiutils.get_two_column_box(guiutils.bold_label(_("File:")), Gtk.Label(label=media_file.name), 120)
row2 = guiutils.get_two_column_box(guiutils.bold_label(_("File Profile:")), Gtk.Label(label=match_profile_name), 120)
row3 = guiutils.get_two_column_box(guiutils.bold_label(_("Project Profile:")), Gtk.Label(label=project_profile_name), 120)
row4 = guiutils.get_left_justified_box([Gtk.Label(_("Using a matching profile is recommended.\n\nThis message is only displayed on first media load for Project."))])
text_panel = Gtk.VBox(False, 2)
text_panel.pack_start(row1, False, False, 0)
text_panel.pack_start(row2, False, False, 0)
text_panel.pack_start(row3, False, False, 0)
text_panel.pack_start(Gtk.Label(" "), False, False, 0)
text_panel.pack_start(row4, False, False, 0)
vbox = dialogutils.get_warning_message_dialog_panel(primary_txt, secondary_txt,
True, None, [text_panel])
alignment = dialogutils.get_default_alignment(vbox)
dialog.vbox.pack_start(alignment, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
_default_behaviour(dialog)
dialog.connect('response', callback, media_file)
dialog.show_all()
def combine_sequences_dialog(callback):
if len(editorstate.PROJECT().sequences) < 2:
primary_txt = _("Cannot import sequence!")
secondary_txt = _("There are no other sequences in the Project.")
dialogutils.info_message(primary_txt, secondary_txt, gui.editor_window.window)
return
dialog = Gtk.Dialog(_("Import Sequence"), gui.editor_window.window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Cancel"), Gtk.ResponseType.REJECT,
_("Import"), Gtk.ResponseType.ACCEPT))
info_text = _("Please note:\n") + \
"\u2022" + _(" It is recommended that you save Project before completing this operation\n") + \
"\u2022" + _(" There is no Undo for this operation\n") + \
"\u2022" + _(" Current Undo Stack will be destroyed\n")
info_label = Gtk.Label(label=info_text)
info_label.set_use_markup(True)
info_box = guiutils.get_left_justified_box([info_label])
action_select = Gtk.ComboBoxText()
action_select.append_text(_("Append Sequence"))
action_select.append_text(_("Insert Sequence at Playhead position"))
action_select.set_active(0)
seq_select = Gtk.ComboBoxText()
selectable_seqs = []
for seq in editorstate.PROJECT().sequences:
if seq != editorstate.current_sequence():
seq_select.append_text(seq.name)
selectable_seqs.append(seq)
seq_select.set_active(0)
row1 = Gtk.HBox(False, 2)
row1.pack_start(Gtk.Label(_("Action:")), False, False, 0)
row1.pack_start(action_select, False, False, 0)
row1.pack_start(guiutils.pad_label(12,2), False, False, 0)
row1.pack_start(Gtk.Label(_("Import:")), False, False, 0)
row1.pack_start(seq_select, False, False, 0)
panel = Gtk.VBox(False, 2)
panel.pack_start(info_box, False, False, 0)
panel.pack_start(row1, False, False, 0)
alignment = dialogutils.get_default_alignment(panel)
dialog.vbox.pack_start(alignment, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
_default_behaviour(dialog)
dialog.connect('response', callback, action_select, seq_select, selectable_seqs)
dialog.show_all()
def set_fades_defaults_dialog(callback):
dialog = Gtk.Dialog(_("Compositors Auto Fades"), gui.editor_window.window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Cancel"), Gtk.ResponseType.REJECT,
_("Set Group Defaults"), Gtk.ResponseType.ACCEPT))
group_select = Gtk.ComboBoxText()
group_select.append_text(_("Dissolve, Blend"))
group_select.append_text(_("Affine Blend, Picture-In-Picture, Region"))
group_select.set_active(0)
groups_vbox = guiutils.get_vbox([group_select], False)
group_frame = panels.get_named_frame(_("Compositor Auto Fades Group"), groups_vbox)
fade_in_row = Gtk.HBox()
fade_in_length_label = Gtk.Label(_("Length:"))
fade_in_check = Gtk.CheckButton.new_with_label (_("Add Fade In on Creation"))
fade_in_spin = Gtk.SpinButton.new_with_range(0, 150, 1)
fade_in_spin.set_value(0)
fade_in_row.pack_start(fade_in_check, False, False, 0)
fade_in_row.pack_start(guiutils.pad_label(12,2), False, False, 0)
fade_in_row.pack_start(fade_in_length_label, False, False, 0)
fade_in_row.pack_start(fade_in_spin, False, False, 0)
fade_out_row = Gtk.HBox()
fade_out_length_label = Gtk.Label(_("Length:"))
fade_out_check = Gtk.CheckButton.new_with_label (_("Add Fade Out on Creation"))
fade_out_spin = Gtk.SpinButton.new_with_range(0, 150, 1)
fade_out_spin.set_value(0)
fade_out_row.pack_start(fade_out_check, False, False, 0)
fade_out_row.pack_start(guiutils.pad_label(12,2), False, False, 0)
fade_out_row.pack_start(fade_out_length_label, False, False, 0)
fade_out_row.pack_start(fade_out_spin, False, False, 0)
widgets = (group_select, fade_in_check, fade_in_spin, fade_out_check, fade_out_spin, fade_in_length_label, fade_out_length_label)
group_select.connect('changed', _fades_group_changed, widgets)
fade_in_check.connect("toggled", _fade_on_off_changed, widgets)
fade_out_check.connect("toggled", _fade_on_off_changed, widgets)
_fades_group_changed(group_select, widgets)
fades_vbox = guiutils.get_vbox([fade_in_row, fade_out_row], False)
fades_frame = panels.get_named_frame(_("Group Auto Fades"), fades_vbox)
vbox = guiutils.get_vbox([group_frame, fades_frame], False)
alignment = dialogutils.get_default_alignment(vbox)
dialogutils.set_outer_margins(dialog.vbox)
dialog.vbox.pack_start(alignment, True, True, 0)
_default_behaviour(dialog)
dialog.connect('response', callback, widgets)
dialog.show_all()
def _fades_group_changed(combo, widgets):
group_select, fade_in_check, fade_in_spin, fade_out_check, fade_out_spin, fade_in_length_label, fade_out_length_label = widgets
if group_select.get_active() == 0:
fade_in_key = appconsts.P_PROP_DISSOLVE_GROUP_FADE_IN
fade_out_key = appconsts.P_PROP_DISSOLVE_GROUP_FADE_OUT
else:
fade_in_key = appconsts.P_PROP_ANIM_GROUP_FADE_IN
fade_out_key = appconsts.P_PROP_ANIM_GROUP_FADE_OUT
fade_in = editorstate.PROJECT().get_project_property(fade_in_key)
fade_out = editorstate.PROJECT().get_project_property(fade_out_key)
if fade_in < 1:
fade_in_check.set_active(False)
fade_in_spin.set_value(0)
fade_in_spin.set_sensitive(False)
fade_in_length_label.set_sensitive(False)
else:
fade_in_check.set_active(True)
fade_in_spin.set_value(fade_in)
fade_in_spin.set_sensitive(True)
fade_in_length_label.set_sensitive(True)
if fade_out < 1:
fade_out_check.set_active(False)
fade_out_spin.set_value(0)
fade_out_spin.set_sensitive(False)
fade_out_length_label.set_sensitive(False)
else:
fade_out_check.set_active(True)
fade_out_spin.set_value(fade_out)
fade_out_spin.set_sensitive(True)
fade_out_length_label.set_sensitive(True)
def _fade_on_off_changed(check_widget, widgets):
group_select, fade_in_check, fade_in_spin, fade_out_check, fade_out_spin, fade_in_length_label, fade_out_length_label = widgets
if check_widget == fade_in_check:
fade_in_spin.set_value(0)
if fade_in_check.get_active() == True:
fade_in_spin.set_sensitive(True)
fade_in_length_label.set_sensitive(True)
else:
fade_in_spin.set_sensitive(False)
fade_in_length_label.set_sensitive(False)
if check_widget == fade_out_check:
fade_out_spin.set_value(0)
if fade_out_check.get_active() == True:
fade_out_spin.set_sensitive(True)
fade_out_length_label.set_sensitive(True)
else:
fade_out_spin.set_sensitive(False)
fade_out_length_label.set_sensitive(False)
def tline_audio_sync_dialog(callback, data):
dialog = Gtk.Dialog(_("Timeline Audio Sync"), gui.editor_window.window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Cancel"), Gtk.ResponseType.REJECT,
_("Do Audio Sync Move Edit"), Gtk.ResponseType.ACCEPT))
media_offsets_label = Gtk.Label(_("Audio Sync Offset between clips media is ") + str(data.media_offset_frames) + _(" frames."))
media_offsets_label.set_use_markup(True)
tline_offsets_label = Gtk.Label(_("Timeline Media Offset between clips is ") + str(data.clip_tline_media_offset) + _(" frames."))
tline_offsets_label.set_use_markup(True)
action_label_text = _("To audio sync clips you need move action origin clip by ") + str(data.clip_tline_media_offset - data.media_offset_frames) + _(" frames.")
action_label = Gtk.Label(action_label_text)
panel_vbox = Gtk.VBox(False, 2)
panel_vbox.pack_start(guiutils.get_left_justified_box([media_offsets_label]), False, False, 0)
panel_vbox.pack_start(guiutils.get_left_justified_box([tline_offsets_label]), False, False, 0)
panel_vbox.pack_start(guiutils.get_pad_label(24, 12), False, False, 0)
panel_vbox.pack_start(guiutils.get_left_justified_box([action_label]), False, False, 0)
panel_vbox.pack_start(guiutils.get_pad_label(24, 24), False, False, 0)
alignment = dialogutils.get_alignment2(panel_vbox)
dialog.vbox.pack_start(alignment, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
_default_behaviour(dialog)
dialog.connect('response', callback, data)
dialog.show_all()
def no_audio_dialog(track):
dialogutils.warning_message(_("Can't put an audio clip on a video track."),
_("Track ")+ utils.get_track_name(track, editorstate.current_sequence()) + _(" is a video track and can't display audio only material."),
gui.editor_window.window)
def confirm_compositing_mode_change(callback, new_compositing_mode):
dialog = Gtk.Dialog(_("Confirm Compositing Mode Change"), gui.editor_window.window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Cancel"), Gtk.ResponseType.REJECT,
_("Change Compositing Mode"), Gtk.ResponseType.ACCEPT))
primary_txt = _("Changing Compositing Mode destroys current Compositors and undo stack")
secondary_txt = _("This operation cannot be undone. Are you sure you wish to proceed?")
warning_panel = dialogutils.get_warning_message_dialog_panel(primary_txt, secondary_txt, is_info=False, alternative_icon=None, panels=None)
alignment = dialogutils.get_alignment2(warning_panel)
dialog.vbox.pack_start(alignment, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
_default_behaviour(dialog)
dialog.connect('response', callback, new_compositing_mode)
dialog.show_all()
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/dialogutils.py 0000664 0000000 0000000 00000020035 13610327166 0025775 0 ustar 00root root 0000000 0000000 """
Flowblade Movie Editor is a nonlinear video editor.
Copyright 2012 Janne Liljeblad.
This file is part of Flowblade Movie Editor .
Flowblade Movie Editor 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.
Flowblade Movie Editor is distributed in the hope that 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 Flowblade Movie Editor. If not, see .
"""
"""
Module contains functions to build generic dialogs.
"""
from gi.repository import GObject
from gi.repository import Gtk
import appconsts
from editorstate import current_sequence
import gui
import guiutils
import utils
def dialog_destroy(dialog, response):
dialog.destroy()
def default_behaviour(dialog):
dialog.set_default_response(Gtk.ResponseType.OK)
dialog.set_resizable(False)
def panel_ok_dialog(title, panel):
dialog = Gtk.Dialog(title, None,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
( _("OK"), Gtk.ResponseType.OK))
alignment = get_default_alignment(panel)
dialog.vbox.pack_start(alignment, True, True, 0)
set_outer_margins(dialog.vbox)
default_behaviour(dialog)
dialog.connect('response', dialog_destroy)
dialog.show_all()
def info_message(primary_txt, secondary_txt, parent_window):
warning_message(primary_txt, secondary_txt, parent_window, is_info=True)
def warning_message(primary_txt, secondary_txt, parent_window, is_info=False):
warning_message_with_callback(primary_txt, secondary_txt, parent_window, is_info, dialog_destroy)
def warning_message_with_callback(primary_txt, secondary_txt, parent_window, is_info, callback):
content = get_warning_message_dialog_panel(primary_txt, secondary_txt, is_info)
dialog = Gtk.Dialog("",
parent_window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
( _("OK"), Gtk.ResponseType.ACCEPT))
alignment = get_default_alignment(content)
dialog.vbox.pack_start(alignment, True, True, 0)
set_outer_margins(dialog.vbox)
dialog.set_resizable(False)
dialog.connect('response', callback)
dialog.show_all()
def warning_message_with_panels(primary_txt, secondary_txt, parent_window, is_info, callback, panels):
content = get_warning_message_dialog_panel(primary_txt, secondary_txt, is_info, None, panels)
dialog = Gtk.Dialog("",
parent_window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
( _("OK"), Gtk.ResponseType.ACCEPT))
alignment = get_default_alignment(content)
dialog.vbox.pack_start(alignment, True, True, 0)
set_outer_margins(dialog.vbox)
dialog.set_resizable(False)
dialog.connect('response', callback)
dialog.show_all()
def warning_confirmation(callback, primary_txt, secondary_txt, parent_window, data=None, is_info=False, use_confirm_text=False):
content = get_warning_message_dialog_panel(primary_txt, secondary_txt, is_info)
align = get_default_alignment(content)
if use_confirm_text == True:
accept_text = _("Confirm")
else:
accept_text = _("OK")
dialog = Gtk.Dialog("",
parent_window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Cancel"), Gtk.ResponseType.REJECT,
accept_text, Gtk.ResponseType.ACCEPT))
dialog.vbox.pack_start(align, True, True, 0)
set_outer_margins(dialog.vbox)
dialog.set_resizable(False)
if data == None:
dialog.connect('response', callback)
else:
dialog.connect('response', callback, data)
dialog.show_all()
def get_warning_message_dialog_panel(primary_txt, secondary_txt, is_info=False, alternative_icon=None, panels=None):
if is_info == True:
icon = Gtk.STOCK_DIALOG_INFO
else:
icon = Gtk.STOCK_DIALOG_WARNING
if alternative_icon != None:
icon = alternative_icon
warning_icon = Gtk.Image.new_from_stock(icon, Gtk.IconSize.DIALOG)
icon_box = Gtk.VBox(False, 2)
icon_box.pack_start(warning_icon, False, False, 0)
icon_box.pack_start(Gtk.Label(), True, True, 0)
p_label = guiutils.bold_label(primary_txt)
s_label = Gtk.Label(label=secondary_txt)
s_label.set_use_markup(True)
texts_pad = Gtk.Label()
texts_pad.set_size_request(12,12)
pbox = Gtk.HBox(False, 1)
pbox.pack_start(p_label, False, False, 0)
pbox.pack_start(Gtk.Label(), True, True, 0)
sbox = Gtk.HBox(False, 1)
sbox.pack_start(s_label, False, False, 0)
sbox.pack_start(Gtk.Label(), True, True, 0)
text_box = Gtk.VBox(False, 0)
text_box.pack_start(pbox, False, False, 0)
text_box.pack_start(texts_pad, False, False, 0)
text_box.pack_start(sbox, False, False, 0)
if panels != None:
for panel in panels:
text_box.pack_start(panel, False, False, 0)
text_box.pack_start(Gtk.Label(), True, True, 0)
hbox = Gtk.HBox(False, 12)
hbox.pack_start(icon_box, False, False, 0)
hbox.pack_start(text_box, True, True, 0)
return hbox
def get_single_line_text_input_dialog(chars, label_width,title, ok_button_text,
label, default_text):
dialog = Gtk.Dialog(title, None,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Cancel"), Gtk.ResponseType.REJECT,
ok_button_text, Gtk.ResponseType.OK))
entry = Gtk.Entry()
entry.set_width_chars(30)
entry.set_text(default_text)
entry.set_activates_default(True)
entry_row = guiutils.get_two_column_box(Gtk.Label(label=label),
entry,
180)
vbox = Gtk.VBox(False, 2)
vbox.pack_start(entry_row, False, False, 0)
vbox.pack_start(guiutils.get_pad_label(12, 12), False, False, 0)
alignment = guiutils.set_margins(vbox, 6, 24, 24, 24)
dialog.vbox.pack_start(alignment, True, True, 0)
set_outer_margins(dialog.vbox)
default_behaviour(dialog)
dialog.set_default_response(Gtk.ResponseType.ACCEPT)
return (dialog, entry)
def get_default_alignment(panel):
alignment = Gtk.Frame.new("") #Gtk.Frame.new(None)
alignment.add(panel)
alignment.set_shadow_type(Gtk.ShadowType.NONE)
guiutils.set_margins(alignment, 12, 24, 12, 18)
return alignment
def get_alignment2(panel):
alignment = Gtk.Frame.new("") #Gtk.Frame.new(None)
alignment.add(panel)
alignment.set_shadow_type(Gtk.ShadowType.NONE)
guiutils.set_margins(alignment, 6, 24, 12, 12)
return alignment
def set_outer_margins(cont):
guiutils.set_margins(cont, 0, 6, 0, 6)
# ------------------------------------------------------------------ delayed window destroying
def delay_destroy_window(window, delay):
GObject.timeout_add(int(delay * 1000), _window_destroy_event, window)
def _window_destroy_event(window):
window.destroy()
# ------------------------------------ track locks handling
# returns True if track locked and displays info
def track_lock_check_and_user_info(track):
if track.edit_freedom == appconsts.LOCKED:
track_name = utils.get_track_name(track, current_sequence())
# No edits on locked tracks.
primary_txt = _("Can't edit a locked track")
secondary_txt = _("Track ") + track_name + _(" is locked. Unlock track to edit it.")
warning_message(primary_txt, secondary_txt, gui.editor_window.window)
return True
return False
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/diskcachemanagement.py 0000664 0000000 0000000 00000016017 13610327166 0027435 0 ustar 00root root 0000000 0000000 """
Flowblade Movie Editor is a nonlinear video editor.
Copyright 2017 Janne Liljeblad.
This file is part of Flowblade Movie Editor .
Flowblade Movie Editor 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.
Flowblade Movie Editor is distributed in the hope that 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 Flowblade Movie Editor. If not, see .
"""
from gi.repository import Gtk
from os import listdir
from os.path import isfile, join
import os
import appconsts
import dialogutils
import gui
import guiutils
import userfolders
NO_WARNING = 0
RECREATE_WARNING = 1
PROJECT_DATA_WARNING = 2
_panels = None
class DiskFolderManagementPanel:
def __init__(self, xdg_folder, folder, info_text, warning_level):
self.xdg_folder = xdg_folder
self.folder = folder
self.warning_level = warning_level
self.destroy_button = Gtk.Button(_("Destroy data"))
self.destroy_button.connect("clicked", self.destroy_pressed)
self.destroy_guard_check = Gtk.CheckButton()
self.destroy_guard_check.set_active(False)
self.destroy_guard_check.connect("toggled", self.destroy_guard_toggled)
self.size_info = Gtk.Label()
self.size_info.set_text(self.get_folder_size_str())
folder_label = Gtk.Label("/" + folder + "")
folder_label.set_use_markup(True)
info = Gtk.HBox(True, 2)
info.pack_start(guiutils.get_left_justified_box([guiutils.bold_label(info_text)]), True, True, 0)
info.pack_start(guiutils.get_left_justified_box([guiutils.pad_label(40, 12), folder_label]), True, True, 0)
info.pack_start(guiutils.get_left_justified_box([guiutils.pad_label(12, 12), self.size_info]), True, True, 0)
button_area = Gtk.HBox(False, 2)
if self.warning_level == PROJECT_DATA_WARNING:
button_area.pack_start(self.destroy_guard_check, True, True, 0)
self.destroy_button.set_sensitive(False)
button_area.pack_start(self.destroy_button, True, True, 0)
if self.warning_level == PROJECT_DATA_WARNING:
warning_icon = Gtk.Image.new_from_stock(Gtk.STOCK_DIALOG_WARNING, Gtk.IconSize.SMALL_TOOLBAR)
warning_icon.set_tooltip_text( _("Destroying this data may change contents of existing\nprojects and make some projects unopenable."))
button_area.pack_start(warning_icon, False, False, 0)
else:
button_area.pack_start(guiutils.pad_label(16, 16), False, False, 0)
button_area.set_size_request(150, 24)
row = Gtk.HBox(False, 2)
row.pack_start(info, True, True, 0)
row.pack_start(button_area, False, False, 0)
self.vbox = Gtk.VBox(False, 2)
self.vbox.pack_start(row, False, False, 0)
def get_disk_folder(self):
cf = self.xdg_folder + self.folder
return cf
def get_folder_files(self):
data_folder = self.get_disk_folder()
return [f for f in listdir(data_folder) if isfile(join(data_folder, f))]
def get_folder_size(self):
files = self.get_folder_files()
size = 0
for f in files:
size += os.path.getsize(self.get_disk_folder() +"/" + f)
return size
def get_folder_size_str(self):
size = self.get_folder_size()
if size > 1000000:
return str(int((size + 500000) / 1000000)) + _(" MB")
elif size > 1000:
return str(int((size + 500) / 1000)) + _(" kB")
else:
return str(int(size)) + " B"
def destroy_pressed(self, widget):
if self.warning_level == NO_WARNING:
# Delete data
self.destroy_data()
return
primaty_text = _("Confirm Destroying Cached Data!")
if self.warning_level == PROJECT_DATA_WARNING:
secondary_text = _("Destroying this data may change contents of existing\nprojects or make some projects unopenable!")
secondary_text += "\n\n"
secondary_text += _("You can use 'File->Save Backup Snapshot...' functionality to backup projects\nso that they can be opened later before destroying this data.")
else:
secondary_text = _("Destroying this data may require parts of it to be recreated later.")
dialogutils. warning_confirmation(self.warning_confirmation, primaty_text, secondary_text, gui.editor_window.window, None, False, True)
def destroy_guard_toggled(self, check_button):
if check_button.get_active() == True:
self.destroy_button.set_sensitive(True)
else:
self.destroy_button.set_sensitive(False)
def warning_confirmation(self, dialog, response_id):
dialog.destroy()
if response_id != Gtk.ResponseType.ACCEPT:
return
self.destroy_data()
def destroy_data(self):
print("deleting ", self.folder)
files = self.get_folder_files()
for f in files:
os.remove(self.get_disk_folder() +"/" + f)
self.size_info.set_text(self.get_folder_size_str())
self.size_info.queue_draw()
def show_disk_management_dialog():
dialog = Gtk.Dialog(_("Disk Cache Manager"), None,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Close"), Gtk.ResponseType.CLOSE))
global _panels
_panels = _get_disk_dir_panels()
pane = Gtk.VBox(True, 2)
for panel in _panels:
pane.pack_start(panel.vbox, True, True, 0)
guiutils.set_margins(pane, 12, 24, 12, 12)
dialog.connect('response', dialogutils.dialog_destroy)
dialog.vbox.pack_start(pane, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
dialogutils.default_behaviour(dialog)
dialog.show_all()
return dialog
def _get_disk_dir_panels():
panels = []
panels.append(DiskFolderManagementPanel(userfolders.get_cache_dir(), appconsts.AUDIO_LEVELS_DIR, _("Audio Levels Data"), RECREATE_WARNING))
panels.append(DiskFolderManagementPanel(userfolders.get_cache_dir(), appconsts.GMIC_DIR, _("G'Mic Tool Session Data"), NO_WARNING))
panels.append(DiskFolderManagementPanel(userfolders.get_data_dir(), appconsts.RENDERED_CLIPS_DIR, _("Rendered Files"), PROJECT_DATA_WARNING))
panels.append(DiskFolderManagementPanel(userfolders.get_cache_dir(), appconsts.THUMBNAILS_DIR, _("Thumbnails"), RECREATE_WARNING))
panels.append(DiskFolderManagementPanel(userfolders.get_data_dir(), appconsts.USER_PROFILES_DIR_NO_SLASH, _("User Created Custom Profiles"), PROJECT_DATA_WARNING))
return panels
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/dnd.py 0000664 0000000 0000000 00000021732 13610327166 0024227 0 ustar 00root root 0000000 0000000 """
Flowblade Movie Editor is a nonlinear video editor.
Copyright 2012 Janne Liljeblad.
This file is part of Flowblade Movie Editor .
Flowblade Movie Editor 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.
Flowblade Movie Editor is distributed in the hope that 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 Flowblade Movie Editor. If not, see .
"""
"""
Module handles drag and drop between widgets.
"""
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import GdkPixbuf
from gi.repository import GLib
import os
import editorstate
import gui
import utils
import respaths
# Source identifiers
SOURCE_MEDIA_FILE = "media_file"
SOURCE_MONITOR_WIDGET = "monitor"
SOURCE_EFFECTS_TREE = "effects"
SOURCE_RANGE_LOG = "range log"
# GUI consts
MEDIA_ICON_WIDTH = 20
MEDIA_ICON_HEIGHT = 15
MEDIA_FILES_DND_TARGET = Gtk.TargetEntry.new('media_file', Gtk.TargetFlags.SAME_APP, 0)
EFFECTS_DND_TARGET = Gtk.TargetEntry.new('effect', Gtk.TargetFlags.SAME_APP, 0)
CLIPS_DND_TARGET = Gtk.TargetEntry.new('clip', Gtk.TargetFlags.SAME_APP, 0)
RANGE_DND_TARGET = Gtk.TargetEntry.new('range', Gtk.TargetFlags.SAME_APP, 0)
URI_DND_TARGET = Gtk.TargetEntry.new('text/uri-list', 0, 0)
# These are used to hold data needed on drag drop instead of the API provided by GtkSelectionData.
drag_data = None
drag_source = None
# Drag icons
clip_icon = None
empty_icon = None
# Callback functions
add_current_effect = None
display_monitor_media_file = None
range_log_items_tline_drop = None
range_log_items_log_drop = None
open_dropped_files = None
def init():
global clip_icon, empty_icon
clip_icon = GdkPixbuf.Pixbuf.new_from_file(respaths.IMAGE_PATH + "clip_dnd.png")
empty_icon = GdkPixbuf.Pixbuf.new_from_file(respaths.IMAGE_PATH + "empty.png")
# ----------------------------------------------- set gui components as drag sources and destinations
def connect_media_files_object_widget(widget):
widget.drag_source_set(Gdk.ModifierType.BUTTON1_MASK,
[MEDIA_FILES_DND_TARGET],
Gdk.DragAction.COPY)
widget.connect("drag_data_get", _media_files_drag_data_get)
widget.drag_source_set_icon_pixbuf(clip_icon)
connect_media_drop_widget(widget)
def connect_media_files_object_cairo_widget(widget):
widget.drag_source_set(Gdk.ModifierType.BUTTON1_MASK,
[MEDIA_FILES_DND_TARGET],
Gdk.DragAction.COPY)
widget.connect("drag_data_get", _media_files_drag_data_get)
widget.drag_source_set_icon_pixbuf(clip_icon)
connect_media_drop_widget(widget)
def connect_media_drop_widget(widget):
widget.drag_dest_set(Gtk.DestDefaults.ALL, [URI_DND_TARGET], Gdk.DragAction.COPY)
widget.drag_dest_add_uri_targets()
widget.connect("drag_data_received", _media_files_drag_received)
def connect_bin_tree_view(treeview, move_files_to_bin_func):
treeview.enable_model_drag_dest([MEDIA_FILES_DND_TARGET],
Gdk.DragAction.DEFAULT)
treeview.connect("drag_data_received", _bin_drag_data_received, move_files_to_bin_func)
def connect_effects_select_tree_view(tree_view):
tree_view.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK,
[EFFECTS_DND_TARGET],
Gdk.DragAction.COPY)
tree_view.connect("drag_data_get", _effects_drag_data_get)
def connect_video_monitor(widget):
widget.drag_dest_set(Gtk.DestDefaults.MOTION | Gtk.DestDefaults.DROP,
[MEDIA_FILES_DND_TARGET],
Gdk.DragAction.COPY)
widget.connect("drag_drop", _on_monitor_drop)
widget.connect("drag_data_get", _save_monitor_media)
widget.drag_source_set(Gdk.ModifierType.BUTTON1_MASK,
[MEDIA_FILES_DND_TARGET],
Gdk.DragAction.COPY)
widget.drag_source_set_icon_pixbuf(clip_icon)
def connect_tline(widget, do_effect_drop_func, do_media_drop_func):
widget.drag_dest_set(Gtk.DestDefaults.MOTION | Gtk.DestDefaults.DROP,
[MEDIA_FILES_DND_TARGET, EFFECTS_DND_TARGET, CLIPS_DND_TARGET],
Gdk.DragAction.COPY)
widget.connect("drag_drop", _on_tline_drop, do_effect_drop_func, do_media_drop_func)
def connect_range_log(treeview):
treeview.drag_source_set(Gdk.ModifierType.BUTTON1_MASK,
[CLIPS_DND_TARGET],
Gdk.DragAction.COPY)
treeview.connect("drag_data_get", _range_log_drag_data_get)
treeview.drag_dest_set(Gtk.DestDefaults.MOTION | Gtk.DestDefaults.DROP,
[RANGE_DND_TARGET],
Gdk.DragAction.COPY)
treeview.connect("drag_drop", _on_range_drop)
treeview.drag_source_set_icon_pixbuf(clip_icon)
def start_tline_clips_out_drag(event, clips, widget):
global drag_data
drag_data = clips
target_list = Gtk.TargetList.new([RANGE_DND_TARGET])
context = widget.drag_begin(target_list, Gdk.DragAction.COPY, 1, event)
# ------------------------------------------------- handlers for drag events
def _media_files_drag_data_get(widget, context, selection, target_id, timestamp):
_save_media_panel_selection()
def _media_files_drag_received(widget, context, x, y, data, info, timestamp):
uris = data.get_uris()
files = []
for uri in uris:
try:
uri_tuple = GLib.filename_from_uri(uri)
except:
continue
uri, unused = uri_tuple
if os.path.exists(uri) == True:
if utils.is_media_file(uri) == True:
files.append(uri)
if len(files) == 0:
return
open_dropped_files(files)
def _range_log_drag_data_get(treeview, context, selection, target_id, timestamp):
_save_treeview_selection(treeview)
global drag_source
drag_source = SOURCE_RANGE_LOG
def _effects_drag_data_get(treeview, context, selection, target_id, timestamp):
_save_treeview_selection(treeview)
global drag_source
drag_source = SOURCE_EFFECTS_TREE
def _on_monitor_drop(widget, context, x, y, timestamp):
context.finish(True, False, timestamp)
if drag_data == None: # A user error drag from monitor to monitor
return
media_file = drag_data[0].media_file
display_monitor_media_file(media_file)
gui.pos_bar.widget.grab_focus()
def _on_effect_stack_drop(widget, context, x, y, timestamp):
context.finish(True, False, timestamp)
add_current_effect()
def _bin_drag_data_received(treeview, context, x, y, selection, info, etime, move_files_to_bin_func):
bin_path, drop_pos = treeview.get_dest_row_at_pos(x, y)
moved_rows = []
for media_object in drag_data:
moved_rows.append(media_object.bin_index)
move_files_to_bin_func(max(bin_path), moved_rows)
def _save_treeview_selection(treeview):
treeselection = treeview.get_selection()
(model, rows) = treeselection.get_selected_rows()
global drag_data
drag_data = rows
def _save_media_panel_selection():
global drag_data, drag_source
drag_data = gui.media_list_view.get_selected_media_objects()
drag_source = SOURCE_MEDIA_FILE
def _save_monitor_media(widget, context, selection, target_id, timestamp):
media_file = editorstate.MONITOR_MEDIA_FILE()
global drag_data, drag_source
drag_data = media_file
drag_source = SOURCE_MONITOR_WIDGET
if media_file == None:
return False
return True
def _on_tline_drop(widget, context, x, y, timestamp, do_effect_drop_func, do_media_drop_func):
if drag_data == None:
context.finish(True, False, timestamp)
return
if drag_source == SOURCE_EFFECTS_TREE:
do_effect_drop_func(x, y)
gui.tline_canvas.widget.grab_focus()
elif drag_source == SOURCE_MEDIA_FILE:
media_file = drag_data[0].media_file
do_media_drop_func(media_file, x, y, True)
gui.tline_canvas.widget.grab_focus()
elif drag_source == SOURCE_MONITOR_WIDGET:
if drag_data != None:
do_media_drop_func(drag_data, x, y, True)
gui.tline_canvas.widget.grab_focus()
else:
print("monitor_drop fail")
elif drag_source == SOURCE_RANGE_LOG:
range_log_items_tline_drop(drag_data, x, y)
else:
print("_on_tline_drop failed to do anything")
context.finish(True, False, timestamp)
def _on_range_drop(widget, context, x, y, timestamp):
range_log_items_log_drop(drag_data)
context.finish(True, False, timestamp)
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/edit.py 0000664 0000000 0000000 00000334263 13610327166 0024415 0 ustar 00root root 0000000 0000000 """
Flowblade Movie Editor is a nonlinear video editor.
Copyright 2012 Janne Liljeblad.
This file is part of Flowblade Movie Editor .
Flowblade Movie Editor 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.
Flowblade Movie Editor is distributed in the hope that 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 Flowblade Movie Editor. If not, see .
"""
"""
Module creates EditAction objects that have user input as input
and sequence state changes as output.
Edits, undos and redos are done by creating and calling methods on these
EditAction objects and placing them on the undo/redo stack.
"""
import appconsts
import clipeffectseditor
import compositeeditor
import compositorfades
from editorstate import current_sequence
from editorstate import get_track
from editorstate import PLAYER
from editorstate import auto_follow_active
import mltfilters
import movemodes
import resync
import tlinewidgets
import trimmodes
import undo
import updater
import utils
# GUI updates are turned off for example when doing resync action
do_gui_update = False
# ---------------------------------- atomic edit ops
def append_clip(track, clip, clip_in, clip_out):
"""
Affects MLT c-struct and python obj values.
"""
clip.clip_in = clip_in
clip.clip_out = clip_out
track.clips.append(clip) # py
track.append(clip, clip_in, clip_out) # mlt
resync.clip_added_to_timeline(clip, track)
def _insert_clip(track, clip, index, clip_in, clip_out):
"""
Affects MLT c-struct and python obj values.
"""
clip.clip_in = clip_in
clip.clip_out = clip_out
track.clips.insert(index, clip) # py
track.insert(clip, index, clip_in, clip_out) # mlt
resync.clip_added_to_timeline(clip, track)
def _insert_blank(track, index, length):
track.insert_blank(index, length - 1) # -1 MLT API says so
blank_clip = track.get_clip(index)
current_sequence().add_clip_attr(blank_clip)
blank_clip.clip_in = 0
blank_clip.clip_out = length - 1 # -1, end inclusive
blank_clip.is_blanck_clip = True
track.clips.insert(index, blank_clip)
def _remove_clip(track, index):
"""
Affects MLT c-struct and python obj values.
"""
track.remove(index)
clip = track.clips.pop(index)
resync.clip_removed_from_timeline(clip)
return clip
# -------------------------------- combined edit ops
def _cut(track, index, clip_cut_frame, clip, clip_copy):
"""
Does cut by removing clip and adding it and copy back
"""
_remove_clip(track, index)
second_out = clip.clip_out # save before insert
_insert_clip(track, clip, index, clip.clip_in, clip_cut_frame - 1)
_insert_clip(track, clip_copy, index + 1, clip_cut_frame, second_out)
def _cut_blank(track, index, clip_cut_frame, clip):
"""
Cuts a blank clip in two.
"""
_remove_clip(track, index)
clip_one_length = clip_cut_frame
clip_two_length = clip.clip_out - clip_cut_frame + 1 # +1 == cut frame part of this clip
track.insert_blank(index, clip_one_length - 1) # -1 MLT api says so
track.insert_blank(index + 1, clip_two_length - 1) # -1 MLT api says so
_add_blank_to_py(track, index, clip_one_length)
_add_blank_to_py(track, index + 1, clip_two_length)
def _add_blank_to_py(track, index, length):
"""
Adds clip data to python side structures for clip that
already exists in MLT data structures
"""
blank_clip = track.get_clip(index)
current_sequence().add_clip_attr(blank_clip)
blank_clip.clip_in = 0
blank_clip.clip_out = length - 1 # -1, end inclusive
blank_clip.is_blanck_clip = True
track.clips.insert(index, blank_clip)
return blank_clip
# --------------------------------- util methods
def _set_in_out(clip, c_in, c_out):
"""
Affects MLT c-struct and python obj values.
"""
clip.clip_in = c_in
clip.clip_out = c_out
clip.set_in_and_out(c_in, c_out)
def _clip_length(clip): # check if can be removed
return clip.clip_out - clip.clip_in + 1 # +1, end inclusive
def _frame_on_cut(clip, clip_frame):
if clip_frame == clip.clip_in:
return True
if clip_frame == clip.clip_out + 1: # + 1 out is inclusive
return True
return False
def _remove_trailing_blanks_undo(self):
for trailing_blank in self.trailing_blanks:
track_index, length = trailing_blank
track = current_sequence().tracks[track_index]
_insert_blank(track, track.count(), length)
def _remove_trailing_blanks_redo(self):
_remove_all_trailing_blanks(self)
def _remove_all_trailing_blanks(self=None):
if self != None:
self.trailing_blanks = []
for i in range(1, len(current_sequence().tracks) - 1): # -1 because hidden track, 1 because black track
try:
track = current_sequence().tracks[i]
last_clip_index = track.count() - 1
clip = track.clips[last_clip_index]
if clip.is_blanck_clip:
length = clip.clip_length()
_remove_clip(track, last_clip_index)
if self != None:
self.trailing_blanks.append((i, length))
except:
pass
def _create_clip_clone(clip):
if clip.media_type != appconsts.PATTERN_PRODUCER:
new_clip = current_sequence().create_file_producer_clip(clip.path, None, False, clip.ttl)
else:
new_clip = current_sequence().create_pattern_producer(clip.create_data)
new_clip.name = clip.name
return new_clip
def _create_mute_volume_filter(seq):
return mltfilters.create_mute_volume_filter(seq)
def _do_clip_mute(clip, volume_filter):
mltfilters.do_clip_mute(clip, volume_filter)
def _do_clip_unmute(clip):
clip.detach(clip.mute_filter.mlt_filter)
clip.mute_filter = None
def _remove_consecutive_blanks(track, index):
lengths = []
while track.clips[index].is_blanck_clip:
lengths.append(track.clips[index].clip_length())
_remove_clip(track, index)
if index == len(track.clips):
break
return lengths
#------------------------------------------------------------- overwrite util methods
def _overwrite_cut_track(track, frame, add_cloned_filters=False):
"""
If frame is on an existing cut, then the method does nothing and returns tuple (-1, -1)
to signal that no cut was made.
If frame is in middle of clip or blank, then the method cuts that item in two
and returns tuple of in and out frames of the clip that was cut as they
were before the cut, for the purpose of having information to do undo later.
If cut was made it also clones fliters to new clip created by cut if requested.
"""
index = track.get_clip_index_at(frame)
clip = track.clips[index]
orig_in_out = (clip.clip_in, clip.clip_out)
clip_start_in_tline = track.clip_start(index)
clip_frame = frame - clip_start_in_tline + clip.clip_in
if not _frame_on_cut(clip, clip_frame):
if clip.is_blank():
add_clip = _cut_blank(track, index, clip_frame, clip)
else:
add_clip = _create_clip_clone(clip)
_cut(track, index, clip_frame, clip, add_clip)
if add_cloned_filters:
clone_filters = current_sequence().clone_filters(clip)
add_clip.filters = clone_filters
_attach_all(add_clip)
return orig_in_out
else:
return (-1, -1)
def _overwrite_cut_range_out(track, self):
# self is the EditAction object
# Cut at out point if not already on cut and out point inside track length
self.orig_out_clip = None
if track.get_length() > self.over_out:
clip_in, clip_out = _overwrite_cut_track(track, self.over_out, True)
self.out_clip_in = clip_in
self.out_clip_length = clip_out - clip_in + 1 # Cut blank can't be reconstructed with clip_in data as it is always 0 for blank, so we use this
if clip_in != -1: # if we did cut we'll need to restore the dut out clip
# which is the original clip because
orig_index = track.get_clip_index_at(self.over_out - 1)
self.orig_out_clip = track.clips[orig_index]
else:
self.out_clip_in = -1
def _overwrite_restore_in(track, moved_index, self):
# self is the EditAction object
in_clip = _remove_clip(track, moved_index - 1)
if not in_clip.is_blanck_clip:
_insert_clip(track, in_clip, moved_index - 1,
in_clip.clip_in, self.in_clip_out)
else: # blanks can't be resized, so put in new blank
_insert_blank(track, moved_index - 1, self.in_clip_out - in_clip.clip_in + 1)
self.removed_clips.pop(0)
def _overwrite_restore_out(track, moved_index, self):
# self is the EditAction object
# If moved clip/s were last in the track and were moved slightly
# forward and were still last in track after move
# this leaves a trailing black that has been removed and this will fail
try:
out_clip = _remove_clip(track, moved_index)
if len(self.removed_clips) > 0: # If overwrite was done inside single clip everything is already in order
if not out_clip.is_blanck_clip:
_insert_clip(track, self.orig_out_clip, moved_index,
self.out_clip_in, out_clip.clip_out)
else: # blanks can't be resized, so put in new blank
_insert_blank(track, moved_index, self.out_clip_length)
self.removed_clips.pop(-1)
except:
pass
#---------------------------------------------- EDIT ACTION
class EditAction:
"""
Packages together edit data and methods to make an undoable
change to sequence.
data - input is dict with named attributes that correspond
to usage in undo_func and redo_func
redo_func is written so that it can be called also when edit is first done
and do_edit() is called.
"""
def __init__(self, undo_func, redo_func, data):
# Functions that change state both ways.
self.undo_func = undo_func
self.redo_func = redo_func
# Grabs data as object members.
self.__dict__.update(data)
# Compositor auto follow is saved with each edit and is computed on first do and later done on redo/undo
self.compositor_autofollow_data = None
# Compositor mode COMPOSITING_MODE_STANDARD_AUTO_FOLLOW requires that compositors without parent clips are destroyed
# when origin clips are destroyed.
self.orphaned_compositors = None
# Some edit redo/undo actions require this but let's only do it when needed.
self.do_restack_compositors = False
# Other then actual trim edits, attempting all edits exits active trimodes and enters _NO_EDIT trim mode.
self.exit_active_trimmode_on_edit = True
# HACK!!!! Overwrite edits crash at redo(sometimes undo) when current frame inside
# affected area if consumer running.
# Remove when fixed in MLT.
self.stop_for_edit = False
self.turn_on_stop_for_edit = False # set true in redo_func for edits that need it
# NEEDED FOR TRIM CRASH HACK, REMOVE IF FIXED IN MLT
# Length of the blank on hidden track covering the whole sequence
# needs to be updated after every edit EXCEPT after trim edits which
# update the hidden track themselves and this flag "update_hidden_track" to False
self.update_hidden_track_blank = True
# Clip effects editor can't handle moving clips between tracks and
# needs to be clearad when clips are moved to another track.
self.clear_effects_editor_for_multitrack_edit = False
def do_edit(self):
if self.exit_active_trimmode_on_edit:
trimmodes.set_no_edit_trim_mode()
self.redo()
undo.register_edit(self)
if self.turn_on_stop_for_edit:
self.stop_for_edit = True
# Create autofollow data if needed and update GUI.
# If autofollow and no data, then GUI update happens in do_edit()
# Added complexity here is to avoid two GUI updates
if auto_follow_active() == True:
self.compositor_autofollow_data, self.orphaned_compositors = get_full_compositor_sync_data()
do_autofollow_redo(self)
if current_sequence().compositing_mode == appconsts.COMPOSITING_MODE_STANDARD_AUTO_FOLLOW:
do_orphaned_compositors_delete_redo(self)
if self.do_restack_compositors == True:
current_sequence().restack_compositors()
self.do_restack_compositors = False # We wish to do this only once
# This wasn't done in redo() because no auto follow data was available
if do_gui_update:
self._update_gui()
def undo(self):
PLAYER().stop_playback()
# HACK, see above in __init()__
if self.stop_for_edit:
PLAYER().consumer.stop()
movemodes.clear_selected_clips() # selection not valid after change in sequence
_remove_trailing_blanks_undo(self)
_consolidate_all_blanks_undo(self)
self.undo_func(self)
_remove_all_trailing_blanks(None)
resync.calculate_and_set_child_clip_sync_states()
if self.compositor_autofollow_data != None:
do_autofollow_undo(self)
if current_sequence().compositing_mode == appconsts.COMPOSITING_MODE_STANDARD_AUTO_FOLLOW:
do_orphaned_compositors_delete_undo(self)
if self.do_restack_compositors == True:
current_sequence().restack_compositors()
# HACK, see above.
if self.stop_for_edit:
PLAYER().consumer.start()
self.do_restack_compositors = False # We wish to do this only once
if do_gui_update:
self._update_gui()
def redo(self):
PLAYER().stop_playback()
# HACK, see above in __init()__
if self.stop_for_edit:
PLAYER().consumer.stop()
movemodes.clear_selected_clips() # selection is not valid after a change in sequence
self.redo_func(self)
_consolidate_all_blanks_redo(self)
_remove_trailing_blanks_redo(self)
resync.calculate_and_set_child_clip_sync_states()
if self.compositor_autofollow_data != None: # This is not called from do_edit() if these exist, we need to do auto follow and orphan compositos management
do_autofollow_redo(self)
if current_sequence().compositing_mode == appconsts.COMPOSITING_MODE_STANDARD_AUTO_FOLLOW:
do_orphaned_compositors_delete_redo(self)
if self.do_restack_compositors == True:
current_sequence().restack_compositors()
tlinewidgets.set_match_frame(-1, -1, True)
# HACK, see above.
if self.stop_for_edit:
PLAYER().consumer.start()
self.do_restack_compositors = False # We wish to do this only once
# Update GUI if no autofollow or if autofollow data is available.
# If autofollow and no data, then GUI update happens in do_edit()
# Added complexity here is to avoid two GUI updates
if ((do_gui_update and auto_follow_active() == False) or
(do_gui_update and auto_follow_active() == True and self.compositor_autofollow_data != None)):
self._update_gui()
def _update_gui(self): # This copied with small modifications into projectaction.py for sequence imports, update there too if needed...yeah.
updater.update_tline_scrollbar() # Slider needs to adjust to possily new program length.
# This REPAINTS TIMELINE as a side effect.
if self.clear_effects_editor_for_multitrack_edit == False:
if current_sequence().clip_is_in_sequence(clipeffectseditor.clip) == True:
updater.update_kf_editor()
clipeffectseditor.reinit_current_effect()
else:
updater.clear_kf_editor()
else:
updater.clear_kf_editor()
current_sequence().update_edit_tracks_length() # NEEDED FOR TRIM CRASH HACK, REMOVE IF FIXED
if self.update_hidden_track_blank:
current_sequence().update_trim_hack_blank_length() # NEEDED FOR TRIM CRASH HACK, REMOVE IF FIXED
PLAYER().display_inside_sequence_length(current_sequence().seq_len) # NEEDED FOR TRIM CRASH HACK, REMOVE IF FIXED
updater.update_seqence_info_text()
# ---------------------------------------------------- compositor sync methods
def get_full_compositor_sync_data():
# Returns list of tuples in form (compositor, orig_in, orig_out, clip_start, clip_end)
# Pair all compositors with their origin clips ids
comp_clip_pairings = {}
orphan_compositors = []
for compositor in current_sequence().compositors:
if compositor.origin_clip_id in comp_clip_pairings:
comp_clip_pairings[compositor.origin_clip_id].append(compositor)
else:
comp_clip_pairings[compositor.origin_clip_id] = [compositor]
# Create resync list
resync_list = []
orphan_origin_clip_ids = list(comp_clip_pairings.keys())
for i in range(current_sequence().first_video_index, len(current_sequence().tracks) - 1): # -1, there is a topmost hidden track
track = current_sequence().tracks[i] # b_track is source track where origin clip is
for j in range(0, len(track.clips)):
clip = track.clips[j]
if clip.id in comp_clip_pairings:
compositor_list = comp_clip_pairings[clip.id]
for compositor in compositor_list:
resync_list.append((clip, track, j, compositor))
if clip.id in orphan_origin_clip_ids:
orphan_origin_clip_ids.remove(clip.id)
# Create orphan compositors list
orhan_compositors = []
if current_sequence().compositing_mode == appconsts.COMPOSITING_MODE_STANDARD_AUTO_FOLLOW:
for oprhan_comp_origin in orphan_origin_clip_ids:
orhan_compositors.append(comp_clip_pairings[oprhan_comp_origin][0])
# Create full data
full_sync_data = []
for resync_item in resync_list:
try:
clip, track, clip_index, compositor = resync_item
clip_start = track.clip_start(clip_index)
clip_end = clip_start + clip.clip_out - clip.clip_in
# Auto fades need to go to start or end of clips and maintain their lengths
if compositor.transition.info.auto_fade_compositor == True:
if compositor.transition.info.name == "##auto_fade_in":
clip_end = clip_start + compositor.get_length() - 1
else:
clip_start = clip_end - compositor.get_length() + 1
orig_in = compositor.clip_in
orig_out = compositor.clip_out
destroy_id = compositor.destroy_id
full_sync_data_item = (destroy_id, orig_in, orig_out, clip_start, clip_end, track.id, compositor.transition.b_track)
full_sync_data.append(full_sync_data_item)
except:
# Clip is probably deleted
pass
return (full_sync_data, orhan_compositors)
def do_autofollow_redo(action_object):
for sync_item in action_object.compositor_autofollow_data:
# real compositor objects get recreated and destroyed all the time and in redo/undo they need to identified by destroy_id
destroy_id, orig_in, orig_out, clip_start, clip_end, clip_track, orig_compositor_track = sync_item
try:
sync_compositor = current_sequence().get_compositor_for_destroy_id(destroy_id)
if sync_compositor.transition.b_track != clip_track and current_sequence().compositing_mode == appconsts.COMPOSITING_MODE_STANDARD_AUTO_FOLLOW:
new_compositor = current_sequence().create_compositor(sync_compositor.type_id)
new_compositor.clone_properties(sync_compositor)
new_compositor.set_in_and_out(sync_compositor.clip_in, sync_compositor.clip_out)
new_compositor.transition.set_tracks(sync_compositor.transition.a_track, clip_track)
current_sequence().remove_compositor(sync_compositor)
current_sequence().add_compositor(new_compositor)
action_object.do_restack_compositors = True
elif sync_compositor.obey_autofollow == True:
sync_compositor.set_in_and_out(clip_start, clip_end)
except Exception as ex:
pass
def do_autofollow_undo(action_object):
for sync_item in action_object.compositor_autofollow_data:
# real compositor objects get recreated and destroyed all the time and in redo/undo they need to identified by destroy_id
destroy_id, orig_in, orig_out, clip_start, clip_end, clip_track, orig_compositor_track = sync_item
try:
sync_compositor = current_sequence().get_compositor_for_destroy_id(destroy_id)
if sync_compositor.transition.b_track != orig_compositor_track and current_sequence().compositing_mode == appconsts.COMPOSITING_MODE_STANDARD_AUTO_FOLLOW:
new_compositor = current_sequence().create_compositor(sync_compositor.type_id)
new_compositor.clone_properties(sync_compositor)
new_compositor.set_in_and_out(sync_compositor.clip_in, sync_compositor.clip_out)
new_compositor.transition.set_tracks(sync_compositor.transition.a_track, orig_compositor_track)
current_sequence().remove_compositor(sync_compositor)
current_sequence().add_compositor(new_compositor)
action_object.do_restack_compositors = True
elif sync_compositor.obey_autofollow == True:
sync_compositor.set_in_and_out(orig_in, orig_out)
except:
# Compositor or clip not found
pass
def do_orphaned_compositors_delete_redo(action_object):
for delete_compositor in action_object.orphaned_compositors:
current_sequence().remove_compositor(delete_compositor)
compositeeditor.maybe_clear_editor(delete_compositor)
action_object.do_restack_compositors = True
def do_orphaned_compositors_delete_undo(action_object):
old_orphaned = action_object.orphaned_compositors
new_orphaned = []
for old_compositor in old_orphaned:
new_ompositor = current_sequence().create_compositor(old_compositor.type_id)
new_ompositor.clone_properties(old_compositor)
new_ompositor.set_in_and_out(old_compositor.clip_in, old_compositor.clip_out)
new_ompositor.transition.set_tracks(old_compositor.transition.a_track, old_compositor.transition.b_track)
current_sequence().add_compositor(new_ompositor)
new_orphaned.append(new_ompositor)
action_object.do_restack_compositors = True
action_object.orphaned_compositors = new_orphaned
# ---------------------------------------------------- SYNC DATA
class SyncData:
"""
Captures sync between two clips, values filled at use sites.
"""
def __init__(self):
self.pos_offset = None
self.clip_in = None
self.clip_out = None
self.master_clip = None
self.master_inframe = None
self.master_audio_index = None # this does nothing? try to remove.
#-------------------- APPEND CLIP
# "track","clip","clip_in","clip_out"
# Appends clip to track
def append_action(data):
action = EditAction(_append_undo,_append_redo, data)
return action
def _append_undo(self):
self.clip = _remove_clip(self.track, len(self.track.clips) - 1)
def _append_redo(self):
self.clip.index = self.track.count()
append_clip(self.track, self.clip, self.clip_in, self.clip_out)
#----------------- REMOVE MULTIPLE CLIPS
# "track","from_index","to_index"
def remove_multiple_action(data):
action = EditAction(_remove_multiple_undo,_remove_multiple_redo, data)
return action
def _remove_multiple_undo(self):
clips_count = self.to_index + 1 - self.from_index # + 1 == to_index inclusive
for i in range(0, clips_count):
add_clip = self.clips[i]
index = self.from_index + i
_insert_clip(self.track, add_clip, index, add_clip.clip_in, \
add_clip.clip_out)
def _remove_multiple_redo(self):
self.clips = []
for i in range(self.from_index, self.to_index + 1):
removed_clip = _remove_clip(self.track, self.from_index)
self.clips.append(removed_clip)
#------------------ COVER DELETE FADE OUT
# "track","clip","index"
def cover_delete_fade_out(data):
action = EditAction(_cover_delete_fade_out_undo,_cover_delete_fade_out_redo, data)
return action
def _cover_delete_fade_out_undo(self):
cover_clip = _remove_clip(self.track, self.index - 1)
_insert_clip(self.track, cover_clip, self.index - 1,
cover_clip.clip_in, self.original_out)
_insert_clip(self.track, self.clip, self.index,
self.clip.clip_in, self.clip.clip_out)
def _cover_delete_fade_out_redo(self):
_remove_clip(self.track, self.index)
cover_clip = _remove_clip(self.track, self.index - 1)
self.original_out = cover_clip.clip_out
_insert_clip(self.track, cover_clip, self.index - 1,
cover_clip.clip_in, cover_clip.clip_out + self.clip.get_length() - 1) # -1, out is iclusive
#------------------ COVER DELETE FADE IN
# "track","clip","index"
def cover_delete_fade_in(data):
action = EditAction(_cover_delete_fade_in_undo,_cover_delete_fade_in_redo, data)
return action
def _cover_delete_fade_in_undo(self):
cover_clip = _remove_clip(self.track, self.index)
_insert_clip(self.track, cover_clip, self.index,
self.original_in, cover_clip.clip_out)
_insert_clip(self.track, self.clip, self.index,
self.clip.clip_in, self.clip.clip_out)
def _cover_delete_fade_in_redo(self):
_remove_clip(self.track, self.index)
cover_clip = _remove_clip(self.track, self.index)
self.original_in = cover_clip.clip_in
_insert_clip(self.track, cover_clip, self.index,
cover_clip.clip_in - self.clip.get_length(), cover_clip.clip_out) # -1, out is iclusive
#------------------ COVER DELETE TRANSITION
# "track", "clip","index","to_part","from_part"
def cover_delete_transition(data):
action = EditAction(_cover_delete_transition_undo, _cover_delete_transition_redo, data)
return action
def _cover_delete_transition_undo(self):
cover_clip_from = _remove_clip(self.track, self.index - 1)
cover_clip_to = _remove_clip(self.track, self.index - 1)
_insert_clip(self.track, cover_clip_from, self.index - 1,
cover_clip_from.clip_in, self.original_from_out)
_insert_clip(self.track, self.clip, self.index,
self.clip.clip_in, self.clip.clip_out)
_insert_clip(self.track, cover_clip_to, self.index + 1,
self.original_to_in, cover_clip_to.clip_out)
def _cover_delete_transition_redo(self):
cover_clip_from = _remove_clip(self.track, self.index - 1)
_remove_clip(self.track, self.index - 1)
cover_clip_to = _remove_clip(self.track, self.index - 1)
self.original_from_out = cover_clip_from.clip_out
self.original_to_in = cover_clip_to.clip_in
_insert_clip(self.track, cover_clip_from, self.index - 1,
cover_clip_from.clip_in, cover_clip_from.clip_out + self.from_part - 1)
_insert_clip(self.track, cover_clip_to, self.index,
cover_clip_to.clip_in - self.to_part, cover_clip_to.clip_out)
#----------------- LIFT MULTIPLE CLIPS
# "track","from_index","to_index"
def lift_multiple_action(data):
action = EditAction(_lift_multiple_undo,_lift_multiple_redo, data)
action.blank_clip = None
return action
def _lift_multiple_undo(self):
# Remove blank
_remove_clip(self.track, self.from_index)
# Insert clips
clips_count = self.to_index + 1 - self.from_index # + 1 == to_index inclusive
for i in range(0, clips_count):
add_clip = self.clips[i]
index = self.from_index + i
_insert_clip(self.track, add_clip, index, add_clip.clip_in, \
add_clip.clip_out)
def _lift_multiple_redo(self):
# Remove clips
self.clips = []
removed_length = 0
for i in range(self.from_index, self.to_index + 1): # + 1 == to_index inclusive
removed_clip = _remove_clip(self.track, self.from_index)
self.clips.append(removed_clip)
removed_length += _clip_length(removed_clip)
# Insert blank
_insert_blank(self.track, self.from_index, removed_length)
#----------------- CUT CLIP
# "track","clip","index","clip_cut_frame"
# Cuts clip at frame by creating two clips and setting ins and outs.
def cut_action(data):
action = EditAction(_cut_undo,_cut_redo, data)
return action
def _cut_undo(self):
_remove_clip(self.track, self.index)
_remove_clip(self.track, self.index)
_insert_clip(self.track, self.clip, self.index, self.clip.clip_in, \
self.new_clip.clip_out)
def _cut_redo(self):
# Create new second clip if does not exist
if(not hasattr(self, "new_clip")):
self.new_clip = _create_clip_clone(self.clip)
_cut(self.track, self.index, self.clip_cut_frame, self.clip, \
self.new_clip)
#----------------- CUT ALL TRACKS
# "tracks_cut_data" which is a list of [{"track","clip","index","clip_cut_frame"}] objects, list of cut data for all tracks
def cut_all_action(data):
action = EditAction(_cut_all_undo,_cut_all_redo, data)
return action
def _cut_all_undo(self):
for i in range(0, len(self.tracks_cut_data)):
track_cut_data = self.tracks_cut_data[i]
if track_cut_data == None: # not all tracks are cut
continue
new_clip = self.new_clips[i]
_remove_clip(track_cut_data["track"], track_cut_data["index"])
_remove_clip(track_cut_data["track"], track_cut_data["index"])
_insert_clip(track_cut_data["track"], track_cut_data["clip"],
track_cut_data["index"], track_cut_data["clip"].clip_in,
new_clip.clip_out)
def _cut_all_redo(self):
# Create new second clips list if does not exist
if(not hasattr(self, "new_clips")):
self.new_clips = []
first_redo = True
else:
first_redo = False
for i in range(0, len(self.tracks_cut_data)):
track_cut_data = self.tracks_cut_data[i]
if track_cut_data == None: # not all tracks are cut
if first_redo == True:
self.new_clips.append(None)
continue
if first_redo == True:
new_clip = _create_clip_clone(track_cut_data["clip"])
self.new_clips.append(new_clip)
else:
new_clip = self.new_clips[i]
_cut(track_cut_data["track"], track_cut_data["index"],
track_cut_data["clip_cut_frame"], track_cut_data["clip"],
new_clip)
#----------------- INSERT CLIP
# "track","clip","index","clip_in","clip_out"
# Inserts clip at index into track
def insert_action(data):
action = EditAction(_insert_undo,_insert_redo, data)
return action
def _insert_undo(self):
_remove_clip(self.track, self.index)
def _insert_redo(self):
_insert_clip(self.track, self.clip, self.index, self.clip_in, self.clip_out)
#----------------- 3 POINT OVERWRITE
# "track","clip", "clip_in","clip_out","in_index","out_index"
def three_point_overwrite_action(data):
action = EditAction(_three_over_undo, _three_over_redo, data)
return action
def _three_over_undo(self):
_remove_clip(self.track, self.in_index)
clips_count = self.out_index + 1 - self.in_index # + 1 == to_index inclusive
for i in range(0, clips_count):
add_clip = self.clips[i]
index = self.in_index + i
_insert_clip(self.track, add_clip, index, add_clip.clip_in, add_clip.clip_out)
def _three_over_redo(self):
# Remove and replace
self.clips = []
for i in range(self.in_index, self.out_index + 1): # + 1 == out_index inclusive
removed_clip = _remove_clip(self.track, i)
self.clips.append(removed_clip)
_insert_clip(self.track, self.clip, self.in_index, self.clip_in, self.clip_out)
#----------------- SYNC OVERWRITE
#"track","clip","clip_in","clip_out","frame"
def sync_overwrite_action(data):
action = EditAction(_sync_over_undo, _sync_over_redo, data)
return action
def _sync_over_undo(self):
# Remove overwrite clip
track = self.track
_remove_clip(track, self.in_index)
# Fix in clip and remove cut created clip if in was cut
if self.in_clip_out != -1:
in_clip = _remove_clip(track, self.in_index - 1)
copy_clip = _create_clip_clone(in_clip)
_insert_clip(track, copy_clip, self.in_index - 1,
in_clip.clip_in, self.in_clip_out)
self.removed_clips.pop(0) # The end half of insert cut
# Fix out clip and remove cut created clip if out was cut
if self.out_clip_in != -1:
try:
out_clip = _remove_clip(track, self.out_index)
copy_clip = _create_clip_clone(out_clip)
if len(self.removed_clips) > 0: # If overwrite was done inside single clip
# we don' need to put end half of out clip back in
_insert_clip(track, copy_clip, self.out_index,
self.out_clip_in, out_clip.clip_out)
self.removed_clips.pop(-1) # Front half of out clip
except:
pass
# Put back old clips
for i in range(0, len(self.removed_clips)):
clip = self.removed_clips[i];
_insert_clip(self.track, clip, self.in_index + i, clip.clip_in,
clip.clip_out)
def _sync_over_redo(self):
# Cut at in point if not already on cut
track = self.track
in_clip_in, in_clip_out = _overwrite_cut_track(track, self.frame)
self.in_clip_out = in_clip_out # out frame of the clip *previous* to overwritten clip after cut
self.over_out = self.frame + self.clip_out - self.clip_in + 1 # +1 out frame incl.
# If out point in track area we need to cut out point too
if track.get_length() > self.over_out:
out_clip_in, out_clip_out = _overwrite_cut_track(track, self.over_out)
self.out_clip_in = out_clip_in
else:
self.out_clip_in = -1
# Splice out clips in overwrite range
self.removed_clips = []
self.in_index = track.get_clip_index_at(self.frame)
self.out_index = track.get_clip_index_at(self.over_out)
for i in range(self.in_index, self.out_index):
removed_clip = _remove_clip(track, self.in_index)
self.removed_clips.append(removed_clip)
#------------------------------------- GAP APPEND
#"track","clip","clip_in","clip_out","frame"
def gap_append_action(data):
action = EditAction(_gap_append_undo, _gap_append_redo, data)
return action
def _gap_append_undo(self):
pass
def _gap_append_redo(self):
pass
#----------------- TWO_ROLL_TRIM
# "track","index","from_clip","to_clip","delta","edit_done_callback"
# "cut_frame"
def tworoll_trim_action(data):
action = EditAction(_tworoll_trim_undo,_tworoll_trim_redo, data)
action.exit_active_trimmode_on_edit = False
action.update_hidden_track_blank = False
return action
def _tworoll_trim_undo(self):
_remove_clip(self.track, self.index)
_remove_clip(self.track, self.index - 1)
if self.non_edit_side_blank == False:
_insert_clip(self.track, self.from_clip, self.index - 1, \
self.from_clip.clip_in, \
self.from_clip.clip_out - self.delta)
_insert_clip(self.track, self.to_clip, self.index, \
self.to_clip.clip_in - self.delta, \
self.to_clip.clip_out )
elif self.to_clip.is_blanck_clip:
_insert_clip(self.track, self.from_clip, self.index - 1, \
self.from_clip.clip_in, \
self.from_clip.clip_out - self.delta)
_insert_blank(self.track, self.index, self.to_length)
else: # from clip is blank
_insert_blank(self.track, self.index - 1, self.from_length)
_insert_clip(self.track, self.to_clip, self.index, \
self.to_clip.clip_in - self.delta, \
self.to_clip.clip_out )
def _tworoll_trim_redo(self):
_remove_clip(self.track, self.index)
_remove_clip(self.track, self.index - 1)
if self.non_edit_side_blank == False:
_insert_clip(self.track, self.from_clip, self.index - 1, \
self.from_clip.clip_in, \
self.from_clip.clip_out + self.delta)
_insert_clip(self.track, self.to_clip, self.index, \
self.to_clip.clip_in + self.delta, \
self.to_clip.clip_out )
elif self.to_clip.is_blanck_clip:
_insert_clip(self.track, self.from_clip, self.index - 1, \
self.from_clip.clip_in, \
self.from_clip.clip_out + self.delta)
self.to_length = self.to_clip.clip_out - self.to_clip.clip_in + 1 # + 1 out incl
_insert_blank(self.track, self.index, self.to_length - self.delta)
else: # from clip is blank
self.from_length = self.from_clip.clip_out - self.from_clip.clip_in + 1 # + 1 out incl
_insert_blank(self.track, self.index - 1, self.from_length + self.delta )
_insert_clip(self.track, self.to_clip, self.index, \
self.to_clip.clip_in + self.delta, \
self.to_clip.clip_out )
if self.first_do == True:
self.first_do = False
self.edit_done_callback(True, self.cut_frame, self.delta, self.track, self.to_side_being_edited)
#----------------- SLIDE_TRIM
# "track","clip","delta","index","first_do","first_do_callback","start_frame_being_viewed"
def slide_trim_action(data):
action = EditAction(_slide_trim_undo,_slide_trim_redo, data)
action.exit_active_trimmode_on_edit = False
action.update_hidden_track_blank = False
return action
def _slide_trim_undo(self):
_remove_clip(self.track, self.index)
_insert_clip(self.track, self.clip, self.index,
self.clip.clip_in - self.delta, self.clip.clip_out - self.delta)
def _slide_trim_redo(self):
_remove_clip(self.track, self.index)
_insert_clip(self.track, self.clip, self.index,
self.clip.clip_in + self.delta, self.clip.clip_out + self.delta)
# Reinit one roll trim
if self.first_do == True:
self.first_do = False
self.first_do_callback(self.track, self.clip, self.index, self.start_frame_being_viewed)
#-------------------- INSERT MOVE
# "track","insert_index","selected_range_in","selected_range_out"
# "move_edit_done_func"
# Splices out clips in range and splices them in at given index
def insert_move_action(data):
action = EditAction(_insert_move_undo,_insert_move_redo, data)
return action
def _insert_move_undo(self):
# remove clips
for i in self.clips:
_remove_clip(self.track, self.real_insert_index)
# insert clips
for i in range(0, len(self.clips)):
clip = self.clips[i]
_insert_clip(self.track, clip, self.selected_range_in + i, \
clip.clip_in, clip.clip_out )
self.move_edit_done_func(self.clips)
def _insert_move_redo(self):
self.clips = []
self.real_insert_index = self.insert_index
clips_length = self.selected_range_out - self.selected_range_in + 1
# if insert after range it is different when clips removed
if self.real_insert_index > self.selected_range_out:
self.real_insert_index -= clips_length
# remove and save clips
for i in range(0, clips_length):
removed_clip = _remove_clip(self.track, self.selected_range_in)
self.clips.append(removed_clip)
# insert clips
for i in range(0, clips_length):
clip = self.clips[i]
_insert_clip(self.track, clip, self.real_insert_index + i, \
clip.clip_in, clip.clip_out )
self.move_edit_done_func(self.clips)
# --------------------------------------- INSERT MULTIPLE
# "track","clips","index"
def insert_multiple_action(data):
action = EditAction(_insert_multiple_undo, _insert_multiple_redo, data)
return action
def _insert_multiple_undo(self):
for i in range(0, len(self.clips)):
_remove_clip(self.track, self.index)
def _insert_multiple_redo(self):
for i in range(0, len(self.clips)):
add_clip = self.clips[i]
index = self.index + i
if isinstance(add_clip, int): # blanks, these represented as int's
_insert_blank(self.track, index, add_clip)
else: # media clips
_insert_clip(self.track, add_clip, index, add_clip.clip_in, add_clip.clip_out)
#-------------------- MULTITRACK INSERT MOVE
# "track","to_track","insert_index","selected_range_in","selected_range_out"
# "move_edit_done_func"
# Splices out clips in range and splices them in at given index
def multitrack_insert_move_action(data):
action = EditAction(_multitrack_insert_move_undo,_multitrack_insert_move_redo, data)
action.clear_effects_editor_for_multitrack_edit = True
return action
def _multitrack_insert_move_undo(self):
# remove clips
for i in self.clips:
_remove_clip(self.to_track, self.insert_index)
# insert clips
for i in range(0, len(self.clips)):
clip = self.clips[i]
_insert_clip(self.track, clip, self.selected_range_in + i, \
clip.clip_in, clip.clip_out )
self.move_edit_done_func(self.clips)
def _multitrack_insert_move_redo(self):
self.clips = []
clips_length = self.selected_range_out - self.selected_range_in + 1
# remove clips
for i in range(0, clips_length):
removed_clip = _remove_clip(self.track, self.selected_range_in)
self.clips.append(removed_clip)
# insert clips
for i in range(0, clips_length):
clip = self.clips[i]
_insert_clip(self.to_track, clip, self.insert_index + i, \
clip.clip_in, clip.clip_out )
self.move_edit_done_func(self.clips)
#----------------- OVERWRITE MOVE
# "track","over_in","over_out","selected_range_in"
# "selected_range_out","move_edit_done_func"
# Lifts clips from track and overwrites part of track with them
def overwrite_move_action(data):
action = EditAction(_overwrite_move_undo, _overwrite_move_redo, data)
return action
def _overwrite_move_undo(self):
track = self.track
# Remove moved clips
moved_clips_count = self.selected_range_out - self.selected_range_in + 1 # + 1 == out inclusive
moved_index = track.get_clip_index_at(self.over_in)
for i in range(0, moved_clips_count):
_remove_clip(track, moved_index)
# Fix in clip and remove cut created clip if in was cut
if self.in_clip_out != -1:
_overwrite_restore_in(track, moved_index, self)
# Fix out clip and remove cut created clip if out was cut
if self.out_clip_in != -1:
_overwrite_restore_out(track, moved_index, self)
# Put back old clips
for i in range(0, len(self.removed_clips)):
clip = self.removed_clips[i]
_insert_clip(track, clip, moved_index + i, clip.clip_in,
clip.clip_out)
# Remove blank from lifted clip
# if moved clip/s were last in track, the clip were trying to remove
# has already been removed so this will fail
try:
_remove_clip(track, self.selected_range_in)
except:
pass
# Put back lifted clips
for i in range(0, len(self.moved_clips)):
clip = self.moved_clips[i];
_insert_clip(track, clip, self.selected_range_in + i, clip.clip_in,
clip.clip_out)
def _overwrite_move_redo(self):
self.moved_clips = []
track = self.track
# Lift moved clips and insert blank in their place
for i in range(self.selected_range_in, self.selected_range_out + 1): # + 1 == out inclusive
removed_clip = _remove_clip(track, self.selected_range_in)
self.moved_clips.append(removed_clip)
removed_length = self.over_out - self.over_in
_insert_blank(track, self.selected_range_in, removed_length)
# Find out if overwrite starts after or on track end and pad track with blanck if so.
if self.over_in >= track.get_length():
self.starts_after_end = True
gap = self.over_out - track.get_length()
_insert_blank(track, len(track.clips), gap)
else:
self.starts_after_end = False
# Cut at in point if not already on cut
clip_in, clip_out = _overwrite_cut_track(track, self.over_in)
self.in_clip_out = clip_out
# Cut at out point if not already on cut and out point inside track length
_overwrite_cut_range_out(track, self)
# Splice out clips in overwrite range
self.removed_clips = []
in_index = track.get_clip_index_at(self.over_in)
out_index = track.get_clip_index_at(self.over_out)
for i in range(in_index, out_index):
removed_clip = _remove_clip(track, in_index)
self.removed_clips.append(removed_clip)
# Insert overwrite clips
for i in range(0, len(self.moved_clips)):
clip = self.moved_clips[i]
_insert_clip(track, clip, in_index + i, clip.clip_in, clip.clip_out)
# HACK, see EditAction for details
self.turn_on_stop_for_edit = True
#----------------- BOX OVERWRITE MOVE
# "box_selection_data","delta"
# Lifts clips from track and overwrites part of track with them for multple tracks
# Move compositors contained by selection too.
def box_overwrite_move_action(data):
action = EditAction(_box_overwrite_move_undo, _box_overwrite_move_redo, data)
action.turn_on_stop_for_edit = True
return action
def _box_overwrite_move_undo(self):
# Do track move edits
for move_data in self.track_moves:
action_object = utils.EmptyClass
action_object.__dict__.update(move_data)
_overwrite_move_undo(action_object)
# Move compositors
for comp in self.box_selection_data.selected_compositors:
comp.move(-self.delta)
def _box_overwrite_move_redo(self):
# Create data for track overwite moves
if not hasattr(self, "track_moves"):
self.track_moves = []
for track_selection in self.box_selection_data.track_selections:
if track_selection.range_frame_in != -1:
track_move_data = {"track":current_sequence().tracks[track_selection.track_id],
"over_in":track_selection.range_frame_in + self.delta,
"over_out":track_selection.range_frame_out + self.delta,
"selected_range_in":track_selection.selected_range_in,
"selected_range_out":track_selection.selected_range_out,
"move_edit_done_func":None}
self.track_moves.append(track_move_data)
else:
# This may not be necessery...but its going in to make sure move_data is always same
for move_data in self.track_moves:
move_data.pop("removed_clips")
# Do track move edits
for move_data in self.track_moves:
action_object = utils.EmptyClass()
action_object.__dict__.update(move_data)
_overwrite_move_redo(action_object)
# Copy data created in _overwrite_move_redo() that is needed in _overwrite_move_undo
move_data.update(action_object.__dict__)
# Move compositors
for comp in self.box_selection_data.selected_compositors:
comp.move(self.delta)
#----------------- MULTITRACK OVERWRITE MOVE
# "track","to_track","over_in","over_out","selected_range_in"
# "selected_range_out","move_edit_done_func"
# Lifts clips from track and overwrites part of track with them
def multitrack_overwrite_move_action(data):
action = EditAction(_multitrack_overwrite_move_undo, _multitrack_overwrite_move_redo, data)
action.clear_effects_editor_for_multitrack_edit = True
return action
def _multitrack_overwrite_move_undo(self):
track = self.track
to_track = self.to_track
# Remove moved clips
moved_clips_count = self.selected_range_out - self.selected_range_in + 1 # + 1 == out inclusive
moved_index = to_track.get_clip_index_at(self.over_in)
for i in range(0, moved_clips_count):
_remove_clip(to_track, moved_index)
# Fix in clip and remove cut created clip if in was cut
if self.in_clip_out != -1:
_overwrite_restore_in(to_track, moved_index, self)
# Fix out clip and remove cut created clip if out was cut
if self.out_clip_in != -1:
_overwrite_restore_out(to_track, moved_index, self)
# Put back old clips
for i in range(0, len(self.removed_clips)):
clip = self.removed_clips[i];
_insert_clip(to_track, clip, moved_index + i, clip.clip_in,
clip.clip_out)
# Remove blank from lifted clip
# if moved clip/s were last in track, the clip were trying to remove
# has already been removed so this will fail
try:
_remove_clip(track, self.selected_range_in)
except:
pass
# Put back lifted clips
for i in range(0, len(self.moved_clips)):
clip = self.moved_clips[i];
_insert_clip(track, clip, self.selected_range_in + i, clip.clip_in,
clip.clip_out)
def _multitrack_overwrite_move_redo(self):
self.moved_clips = []
track = self.track
to_track = self.to_track
# Lift moved clips and insert blank
for i in range(self.selected_range_in, self.selected_range_out + 1): # + 1 == out inclusive
removed_clip = _remove_clip(track, self.selected_range_in) # THIS LINE BUGS SOMETIMES FIND OUT WHY
self.moved_clips.append(removed_clip)
removed_length = self.over_out - self.over_in
_insert_blank(track, self.selected_range_in, removed_length)
# Find out if overwrite starts after track end and pad track with blank if so
if self.over_in >= to_track.get_length():
self.starts_after_end = True
gap = self.over_out - to_track.get_length()
_insert_blank(to_track, len(to_track.clips), gap)
else:
self.starts_after_end = False
# Cut at in point if not already on cut
clip_in, clip_out = _overwrite_cut_track(to_track, self.over_in)
self.in_clip_out = clip_out
# Cut at out point if not already on cut
_overwrite_cut_range_out(to_track, self)
# Splice out clips in overwrite range
self.removed_clips = []
in_index = to_track.get_clip_index_at(self.over_in)
out_index = to_track.get_clip_index_at(self.over_out)
for i in range(in_index, out_index):
removed_clip = _remove_clip(to_track, in_index)
self.removed_clips.append(removed_clip)
# Insert overwrite clips
for i in range(0, len(self.moved_clips)):
clip = self.moved_clips[i]
_insert_clip(to_track, clip, in_index + i, clip.clip_in, clip.clip_out)
# HACK, see EditAction for details
self.turn_on_stop_for_edit = True
#-------------------------------------------- MULTI MOVE
# "multi_data", "edit_delta"
# self.multi_data is multimovemode.MultimoveData
def multi_move_action(data):
action = EditAction(_multi_move_undo, _multi_move_redo, data)
return action
def _multi_move_undo(self):
track_moved = self.multi_data.track_affected
tracks = current_sequence().tracks
for i in range(1, len(tracks) - 1):
if not track_moved[i - 1]:
continue
track = tracks[i]
edit_op = self.multi_data.track_edit_ops[i - 1]
trim_blank_index = self.multi_data.trim_blank_indexes[i - 1]
if edit_op == appconsts.MULTI_NOOP:
continue
elif edit_op == appconsts.MULTI_TRIM:
blank_length = track.clips[trim_blank_index].clip_length()
_remove_clip(track, trim_blank_index)
_insert_blank(track, trim_blank_index, blank_length - self.edit_delta)
elif edit_op == appconsts.MULTI_ADD_TRIM:
_remove_clip(track, trim_blank_index)
elif edit_op == appconsts.MULTI_TRIM_REMOVE:
if self.edit_delta != -self.multi_data.max_backwards:
_remove_clip(track, trim_blank_index)
_insert_blank(track, trim_blank_index, self.orig_length)
tracks_compositors = _get_tracks_compositors_list()
for i in range(1, len(tracks) - 1):
if not track_moved[i - 1]:
continue
track_comp = tracks_compositors[i - 1]
for comp in track_comp:
if comp.clip_in >= self.multi_data.first_moved_frame + self.edit_delta:
comp.move(-self.edit_delta)
def _multi_move_redo(self):
tracks = current_sequence().tracks
track_moved = self.multi_data.track_affected
# Move clips
for i in range(1, len(tracks) - 1):
if not track_moved[i - 1]:
continue
track = tracks[i]
edit_op = self.multi_data.track_edit_ops[i - 1]
trim_blank_index = self.multi_data.trim_blank_indexes[i - 1]
if edit_op == appconsts.MULTI_NOOP:
continue
elif edit_op == appconsts.MULTI_TRIM:
blank_length = track.clips[trim_blank_index].clip_length()
_remove_clip(track, trim_blank_index)
_insert_blank(track, trim_blank_index, blank_length + self.edit_delta)
elif edit_op == appconsts.MULTI_ADD_TRIM:
_insert_blank(track, trim_blank_index, self.edit_delta)
elif edit_op == appconsts.MULTI_TRIM_REMOVE:
self.orig_length = track.clips[trim_blank_index].clip_length()
_remove_clip(track, trim_blank_index)
if self.edit_delta != -self.multi_data.max_backwards:
_insert_blank(track, trim_blank_index, self.orig_length + self.edit_delta)
# Move compositors
tracks_compositors = _get_tracks_compositors_list()
for i in range(1, len(tracks) - 1):
if not track_moved[i - 1]:
continue
track_comp = tracks_compositors[i - 1]
for comp in track_comp:
if comp.clip_in >= self.multi_data.first_moved_frame:
comp.move(self.edit_delta)
def _get_tracks_compositors_list():
tracks_list = []
tracks = current_sequence().tracks
compositors = current_sequence().compositors
for track_index in range(1, len(tracks) - 1):
track_compositors = []
for j in range(0, len(compositors)):
comp = compositors[j]
if comp.transition.b_track == track_index:
track_compositors.append(comp)
tracks_list.append(track_compositors)
return tracks_list
#-------------------------------------------- RIPPLE TRIM END
# "track","clip","index","edit_delta","first_do","multi_data"
# self.multi_data is trimmodes.RippleData
def ripple_trim_end_action(data):
action = EditAction(_ripple_trim_end_undo, _ripple_trim_end_redo, data)
action.exit_active_trimmode_on_edit = False
action.update_hidden_track_blank = False
return action
def _ripple_trim_end_undo(self):
_remove_clip(self.track, self.index)
_insert_clip(self.track, self.clip, self.index,
self.clip.clip_in, self.clip.clip_out - self.edit_delta)
_ripple_trim_blanks_undo(self)
def _ripple_trim_end_redo(self):
_remove_clip(self.track, self.index)
_insert_clip(self.track, self.clip, self.index,
self.clip.clip_in, self.clip.clip_out + self.edit_delta)
_ripple_trim_blanks_redo(self)
# Reinit one roll trim
if self.first_do == True:
self.first_do = False
self.undo_done_callback(self.track, self.index + 1, False)
#-------------------------------------------- RIPPLE TRIM START
# "track","clip","index","edit_delta","first_do","multi_data"
# self.multi_data is trimmodes.RippleData
def ripple_trim_start_action(data):
action = EditAction(_ripple_trim_start_undo,_ripple_trim_start_redo, data)
action.exit_active_trimmode_on_edit = False
action.update_hidden_track_blank = False
return action
def _ripple_trim_start_undo(self):
_remove_clip(self.track, self.index)
_insert_clip(self.track, self.clip, self.index,
self.clip.clip_in - self.edit_delta, self.clip.clip_out)
_ripple_trim_blanks_undo(self, True)
def _ripple_trim_start_redo(self):
_remove_clip(self.track, self.index)
_insert_clip(self.track, self.clip, self.index,
self.clip.clip_in + self.edit_delta, self.clip.clip_out)
_ripple_trim_blanks_redo(self, True)
# Reinit one roll trim, when used with clip start drag this is not needed
if hasattr(self, "first_do") and self.first_do == True:
self.first_do = False
self.undo_done_callback(self.track, self.index, True)
#------------------ RIPPLE TRIM LAST CLIP END
# "track","clip","index","edit_delta","first_do","multi_data"
# self.multi_data is trimmodes.RippleData
def ripple_trim_last_clip_end_action(data):
action = EditAction(_ripple_trim_last_clip_end_undo,_ripple_trim_last_clip_end_redo, data)
action.exit_active_trimmode_on_edit = False
action.update_hidden_track_blank = False
return action
def _ripple_trim_last_clip_end_undo(self):
_remove_clip(self.track, self.index)
_insert_clip(self.track, self.clip, self.index,
self.clip.clip_in, self.clip.clip_out - self.edit_delta)
_ripple_trim_blanks_undo(self)
def _ripple_trim_last_clip_end_redo(self):
_remove_clip(self.track, self.index)
_insert_clip(self.track, self.clip, self.index,
self.clip.clip_in, self.clip.clip_out + self.edit_delta)
_ripple_trim_blanks_redo(self)
# Reinit one roll trim for continued trim mode, whenused with clip end drag this is not needed
if hasattr(self, "first_do") and self.first_do == True:
self.first_do = False
self.undo_done_callback(self.track)
# ----------------------------- RIPPLE TRIM BLANK UPDATE METHODS
def _ripple_trim_blanks_undo(self, reverse_comp_delta=False):
track_moved = self.multi_data.track_affected
tracks = current_sequence().tracks
applied_delta = self.edit_delta
for i in range(1, len(tracks) - 1):
if not track_moved[i - 1]:
continue
if self.track.id == i:
continue
track = tracks[i]
edit_op = self.multi_data.track_edit_ops[i - 1]
trim_blank_index = self.multi_data.trim_blank_indexes[i - 1]
if edit_op == appconsts.MULTI_NOOP:
continue
elif edit_op == appconsts.MULTI_TRIM:
blank_length = track.clips[trim_blank_index].clip_length()
_remove_clip(track, trim_blank_index)
_insert_blank(track, trim_blank_index, blank_length - applied_delta)
elif edit_op == appconsts.MULTI_ADD_TRIM:
_remove_clip(track, trim_blank_index)
elif edit_op == appconsts.MULTI_TRIM_REMOVE:
if reverse_comp_delta:
if -self.edit_delta != -self.multi_data.max_backwards:
_remove_clip(track, trim_blank_index)
else:
if self.edit_delta != -self.multi_data.max_backwards:
_remove_clip(track, trim_blank_index)
_insert_blank(track, trim_blank_index, self.orig_length)
if reverse_comp_delta:
applied_delta = -applied_delta
_ripple_trim_compositors_move(self, -applied_delta)
def _ripple_trim_blanks_redo(self, reverse_delta=False):
tracks = current_sequence().tracks
track_moved = self.multi_data.track_affected
applied_delta = self.edit_delta
if reverse_delta:
applied_delta = -applied_delta
for i in range(1, len(tracks) - 1):
if not track_moved[i - 1]:
continue
if self.track.id == i:
continue
track = tracks[i]
edit_op = self.multi_data.track_edit_ops[i - 1]
trim_blank_index = self.multi_data.trim_blank_indexes[i - 1]
if edit_op == appconsts.MULTI_NOOP: # no blank clip on this track is not changed
continue
elif edit_op == appconsts.MULTI_TRIM: #longer available blank than max_backwards, lenth is changed
blank_length = track.clips[trim_blank_index].clip_length()
_remove_clip(track, trim_blank_index)
_insert_blank(track, trim_blank_index, blank_length + applied_delta)
elif edit_op == appconsts.MULTI_ADD_TRIM:# no blank to trim available, only possibnle edit is to add blank
_insert_blank(track, trim_blank_index, applied_delta)
elif edit_op == appconsts.MULTI_TRIM_REMOVE: # blank is trimmed if not max length triom, if so, blank is removed
self.orig_length = track.clips[trim_blank_index].clip_length()
_remove_clip(track, trim_blank_index)
if applied_delta != -self.multi_data.max_backwards:
_insert_blank(track, trim_blank_index, self.orig_length + applied_delta)
_ripple_trim_compositors_move(self, applied_delta)
def _ripple_trim_compositors_move(self, delta):
comp_ids = self.multi_data.moved_compositors_destroy_ids
tracks_compositors = _get_tracks_compositors_list()
track_moved = self.multi_data.track_affected
for i in range(1, len(current_sequence().tracks) - 1):
if not track_moved[i - 1]:
continue
track_comps = tracks_compositors[i - 1]
for comp in track_comps:
if comp.destroy_id in comp_ids:
comp.move(delta)
#------------------ TRIM CLIP START
# "track","clip","index","delta","first_do"
# "undo_done_callback" <- THIS IS REALLY BADLY NAMED, IT SHOULD BE FIRST DO CALLBACK
# Trims start of clip
def trim_start_action(data):
action = EditAction(_trim_start_undo,_trim_start_redo, data)
action.exit_active_trimmode_on_edit = False
action.update_hidden_track_blank = False
return action
def _trim_start_undo(self):
_remove_clip(self.track, self.index)
_insert_clip(self.track, self.clip, self.index,
self.clip.clip_in - self.delta, self.clip.clip_out)
def _trim_start_redo(self):
_remove_clip(self.track, self.index)
_insert_clip(self.track, self.clip, self.index,
self.clip.clip_in + self.delta, self.clip.clip_out)
# Reinit one roll trim, when used with clip start drag this is not needed
if hasattr(self, "first_do") and self.first_do == True:
self.first_do = False
self.undo_done_callback(self.track, self.index, True)
#------------------ TRIM CLIP END
# "track","clip","index","delta", "first_do"
# "undo_done_callback" <- THIS IS REALLY BADLY NAMED, IT SHOULD BE FIRST DO CALLBACK
# Trims end of clip
def trim_end_action(data):
action = EditAction(_trim_end_undo,_trim_end_redo, data)
action.exit_active_trimmode_on_edit = False
action.update_hidden_track_blank = False
return action
def _trim_end_undo(self):
_remove_clip(self.track, self.index)
_insert_clip(self.track, self.clip, self.index,
self.clip.clip_in, self.clip.clip_out - self.delta)
def _trim_end_redo(self):
_remove_clip(self.track, self.index)
_insert_clip(self.track, self.clip, self.index,
self.clip.clip_in, self.clip.clip_out + self.delta)
# Reinit one roll trim
if self.first_do == True:
self.first_do = False
self.undo_done_callback(self.track, self.index + 1, False)
#------------------ TRIM LAST CLIP END
# "track","clip","index","delta", "first_do"
# "undo_done_callback" <- THIS IS BADLY NAMED, IT SHOULD BE FIRST DO CALLBACK
def trim_last_clip_end_action(data):
action = EditAction(_trim_last_clip_end_undo,_trim_last_clip_end_redo, data)
action.exit_active_trimmode_on_edit = False
return action
def _trim_last_clip_end_undo(self):
_remove_clip(self.track, self.index)
_insert_clip(self.track, self.clip, self.index,
self.clip.clip_in, self.clip.clip_out - self.delta)
def _trim_last_clip_end_redo(self):
_remove_clip(self.track, self.index)
_insert_clip(self.track, self.clip, self.index,
self.clip.clip_in, self.clip.clip_out + self.delta)
# Reinit one roll trim for continued trim mode
if hasattr(self, "first_do") and self.first_do == True:
self.first_do = False
self.undo_done_callback(self.track)
#------------------ SET CLIP LENGTH
# "track","clip","index","length"
# Trims end of clip
def set_clip_length_action(data):
action = EditAction(_set_clip_length_undo,_set_clip_length_redo, data)
return action
def _set_clip_length_undo(self):
_remove_clip(self.track, self.index)
_insert_clip(self.track, self.clip, self.index,
self.clip.clip_in, self.orig_clip_out)
def _set_clip_length_redo(self):
self.orig_clip_out = self.clip.clip_out
_remove_clip(self.track, self.index)
_insert_clip(self.track, self.clip, self.index,
self.clip.clip_in, self.clip.clip_in + self.length - 1) # -1, out is inclusive and we're usin length here
# ----------------------------------- CLIP END DRAG ON BLANK
# "track","index","clip","blank_clip_length","delta"
def clip_end_drag_on_blank_action(data):
action = EditAction(_clip_end_drag_on_blank_undo, _clip_end_drag_on_blank_redo, data)
return action
def _clip_end_drag_on_blank_undo(self):
_remove_clip(self.track, self.index)
_remove_clip(self.track, self.index)
_insert_clip(self.track, self.clip, self.index,
self.clip.clip_in, self.orig_out)
_insert_blank(self.track, self.index + 1, self.blank_clip_length)
def _clip_end_drag_on_blank_redo(self):
_remove_clip(self.track, self.index)
_remove_clip(self.track, self.index)
self.orig_out = self.clip.clip_out
_insert_clip(self.track, self.clip, self.index,
self.clip.clip_in, self.clip.clip_out + self.delta)
_insert_blank(self.track, self.index + 1, self.blank_clip_length - self.delta)
# ----------------------------------- CLIP END DRAG REPLACE BLANK
# "track","index","clip","blank_clip_length","delta"
def clip_end_drag_replace_blank_action(data):
action = EditAction(_clip_end_drag_replace_blank_undo, _clip_end_drag_replace_blank_redo, data)
return action
def _clip_end_drag_replace_blank_undo(self):
_remove_clip(self.track, self.index)
_insert_clip(self.track, self.clip, self.index,
self.clip.clip_in, self.orig_out)
_insert_blank(self.track, self.index + 1, self.blank_clip_length)
def _clip_end_drag_replace_blank_redo(self):
_remove_clip(self.track, self.index)
_remove_clip(self.track, self.index)
self.orig_out = self.clip.clip_out
_insert_clip(self.track, self.clip, self.index,
self.clip.clip_in, self.clip.clip_out + self.delta)
# ----------------------------------- CLIP START DRAG ON BLANK
# "track","index","clip","blank_clip_length","delta"
def clip_start_drag_on_blank_action(data):
action = EditAction(_clip_start_drag_on_blank_undo, _clip_start_drag_on_blank_redo, data)
return action
def _clip_start_drag_on_blank_undo(self):
_remove_clip(self.track, self.index - 1)
_remove_clip(self.track, self.index - 1)
_insert_blank(self.track, self.index - 1, self.blank_clip_length)
_insert_clip(self.track, self.clip, self.index,
self.orig_in, self.clip.clip_out)
def _clip_start_drag_on_blank_redo(self):
_remove_clip(self.track, self.index - 1)
_remove_clip(self.track, self.index - 1)
self.orig_in = self.clip.clip_in
_insert_blank(self.track, self.index - 1, self.blank_clip_length + self.delta)
_insert_clip(self.track, self.clip, self.index,
self.clip.clip_in + self.delta, self.clip.clip_out)
# ----------------------------------- CLIP START DRAG REPLACE BLANK
# "track","index","clip","blank_clip_length","delta"
def clip_start_drag_replace_blank_action(data):
action = EditAction(_clip_start_drag_replace_blank_undo, _clip_start_drag_replace_blank_redo, data)
return action
def _clip_start_drag_replace_blank_undo(self):
_remove_clip(self.track, self.index - 1)
_insert_blank(self.track, self.index - 1, self.blank_clip_length)
_insert_clip(self.track, self.clip, self.index,
self.orig_in, self.clip.clip_out)
def _clip_start_drag_replace_blank_redo(self):
_remove_clip(self.track, self.index - 1)
_remove_clip(self.track, self.index - 1)
self.orig_in = self.clip.clip_in
_insert_clip(self.track, self.clip, self.index - 1,
self.clip.clip_in + self.delta, self.clip.clip_out)
#------------------- ADD FILTER
# "clip","filter_info","filter_edit_done_func"
# Adds filter to clip.
def add_filter_action(data):
action = EditAction(_add_filter_undo,_add_filter_redo, data)
return action
def _add_filter_undo(self):
self.clip.detach(self.filter_object.mlt_filter)
index = self.clip.filters.index(self.filter_object)
self.clip.filters.pop(index)
self.filter_edit_done_func(self.clip, len(self.clip.filters) - 1) # updates effect stack gui
def _add_filter_redo(self):
try: # is redo, fails for first
self.clip.attach(self.filter_object.mlt_filter)
self.clip.filters.append(self.filter_object)
except: # First do
self.filter_object = current_sequence().create_filter(self.filter_info)
self.clip.attach(self.filter_object.mlt_filter)
self.clip.filters.append(self.filter_object)
self.filter_edit_done_func(self.clip, len(self.clip.filters) - 1) # updates effect stack gui
#------------------- ADD MULTIPART FILTER
# "clip","filter_info","filter_edit_done_func"
# Adds filter to clip.
def add_multipart_filter_action(data):
action = EditAction(_add_multipart_filter_undo,_add_multipart_filter_redo, data)
return action
def _add_multipart_filter_undo(self):
self.filter_object.detach_all_mlt_filters(self.clip)
index = self.clip.filters.index(self.filter_object)
self.clip.filters.pop(index)
self.filter_edit_done_func(self.clip, len(self.clip.filters) - 1) # updates effect stack
def _add_multipart_filter_redo(self):
try: # if redo, fails for first
self.filter_object.attach_filters(self.clip)
self.clip.filters.append(self.filter_object)
except: # First do
self.filter_object = current_sequence().create_multipart_filter(self.filter_info, self.clip)
self.filter_object.attach_all_mlt_filters(self.clip)
self.clip.filters.append(self.filter_object)
self.filter_edit_done_func(self.clip, len(self.clip.filters) - 1) # updates effect stack
#------------------- REMOVE FILTER
# "clip","index","filter_edit_done_func"
# Adds filter to clip.
def remove_filter_action(data):
action = EditAction(_remove_filter_undo,_remove_filter_redo, data)
return action
def _remove_filter_undo(self):
_detach_all(self.clip)
try:
self.clip.filters.insert(self.index, self.filter_object)
except:
self.clip.filters.append(self.filter_object)
_attach_all(self.clip)
self.filter_edit_done_func(self.clip,self.index) # updates effect stack gui if needed
def _remove_filter_redo(self):
_detach_all(self.clip)
self.filter_object = self.clip.filters.pop(self.index)
_attach_all(self.clip)
self.filter_edit_done_func(self.clip, len(self.clip.filters) - 1)# updates effect stack gui
#------------------- MOVE FILTER
# "clip",""insert_index","delete_index"","filter_edit_done_func"
# Moves filter in filter stack filter to clip.
def move_filter_action(data):
action = EditAction(_move_filter_undo,_move_filter_redo, data)
return action
def _move_filter_undo(self):
_detach_all(self.clip)
for i in range(0, len(self.filters_orig)):
self.clip.filters.pop(0)
for i in range(0, len(self.filters_orig)):
self.clip.filters.append(self.filters_orig[i])
if self.delete_index < self.insert_index:
active_index = self.delete_index
else:
active_index = self.delete_index - 1
_attach_all(self.clip)
self.filter_edit_done_func(self.clip, active_index)
def _move_filter_redo(self):
_detach_all(self.clip)
# Copy filters in original order for undo
self.filters_orig = []
for i in range(0, len(self.clip.filters)):
self.filters_orig.append(self.clip.filters[i])
if self.delete_index < self.insert_index:
# d < i, moved filter can be found at d
moved_filter = self.clip.filters[self.delete_index]
_filter_move_insert(self.clip.filters, moved_filter, self.insert_index)
self.clip.filters.pop(self.delete_index)
active_index = self.insert_index - 1
else:
# d > i, moved filter can be found at d - 1
moved_filter = self.clip.filters[self.delete_index - 1]
_filter_move_insert(self.clip.filters, moved_filter, self.insert_index)
self.clip.filters.pop(self.delete_index)
active_index = self.insert_index
_attach_all(self.clip)
self.filter_edit_done_func(self.clip, active_index)
def _detach_all(clip):
mltfilters.detach_all_filters(clip)
def _attach_all(clip):
mltfilters.attach_all_filters(clip)
def _filter_move_insert(filters_list, f, insert_index):
try:
filters_list.insert(insert_index, f)
except:
filters_list.append(insert_index, f)
#------------------- REMOVE MULTIPLE FILTERS
# "clips"
# Adds filter to clip.
def remove_multiple_filters_action(data):
action = EditAction(_remove_multiple_filters_undo,_remove_multiple_filters_redo, data)
return action
def _remove_multiple_filters_undo(self):
for clip, clip_filters in zip(self.clips, self.clip_filters):
clip.filters = clip_filters
_attach_all(clip)
def _remove_multiple_filters_redo(self):
self.clip_filters = []
for clip in self.clips:
_detach_all(clip)
self.clip_filters.append(clip.filters)
clip.filters = []
updater.clear_clip_from_editors(clip)
# -------------------------------------- CLONE FILTERS
# "clip","clone_source_clip"
def clone_filters_action(data):
action = EditAction(_clone_filters_undo, _clone_filters_redo, data)
return action
def _clone_filters_undo(self):
_detach_all(self.clip)
self.clip.filters = self.old_filters
_attach_all(self.clip)
def _clone_filters_redo(self):
if not hasattr(self, "clone_filters"):
self.clone_filters = current_sequence().clone_filters(self.clone_source_clip)
self.old_filters = self.clip.filters
_detach_all(self.clip)
self.clip.filters = self.clone_filters
_attach_all(self.clip)
# -------------------------------------- PASTE FILTERS
# "clip","clone_source_clip"
def paste_filters_action(data):
action = EditAction(_paste_filters_undo, _paste_filters_redo, data)
return action
def _paste_filters_undo(self):
_detach_all(self.clip)
self.clip.filters = self.old_filters
_attach_all(self.clip)
def _paste_filters_redo(self):
if not hasattr(self, "clone_filters"):
self.clone_filters = current_sequence().clone_filters(self.clone_source_clip)
self.old_filters = self.clip.filters
_detach_all(self.clip)
new_filters = self.old_filters + self.clone_filters
self.clip.filters = new_filters
_attach_all(self.clip)
# -------------------------------------- ADD COMPOSITOR ACTION
# "origin_clip_id",in_frame","out_frame","compositor_type","a_track","b_track", "clip"
def add_compositor_action(data):
action = EditAction(_add_compositor_undo, _add_compositor_redo, data)
action.first_do = True
return action
def _add_compositor_undo(self):
current_sequence().remove_compositor(self.compositor)
current_sequence().restack_compositors()
self.old_compositor = self.compositor # maintain compositor property values though full undo/redo sequence
compositeeditor.maybe_clear_editor(self.compositor)
self.compositor = None
def _add_compositor_redo(self):
self.compositor = current_sequence().create_compositor(self.compositor_type)
if hasattr(self, "old_compositor"): # maintain compositor property values though full undo/redo sequence
self.compositor.clone_properties(self.old_compositor)
self.compositor.transition.set_tracks(self.a_track, self.b_track)
self.compositor.set_in_and_out(self.in_frame, self.out_frame)
self.compositor.origin_clip_id = self.origin_clip_id
# Compositors are recreated continually in sequence.restack_compositors() and cannot be identified for undo/redo using object identity
# so these ids must be preserved for all successive versions of a compositor
if self.first_do == True:
self.destroy_id = self.compositor.destroy_id
self.first_do = False
else:
self.compositor.destroy_id = self.destroy_id
current_sequence().add_compositor(self.compositor)
current_sequence().restack_compositors()
self.compositor.update_autofade_keyframes()
compositeeditor.set_compositor(self.compositor)
# -------------------------------------- DELETE COMPOSITOR ACTION
# "compositor"
def delete_compositor_action(data):
action = EditAction(_delete_compositor_undo, _delete_compositor_redo, data)
action.first_do = True
return action
def _delete_compositor_undo(self):
old_compositor = self.compositor
self.compositor = current_sequence().create_compositor(old_compositor.type_id)
self.compositor.clone_properties(old_compositor)
self.compositor.set_in_and_out(old_compositor.clip_in, old_compositor.clip_out)
self.compositor.transition.set_tracks(old_compositor.transition.a_track, old_compositor.transition.b_track)
current_sequence().add_compositor(self.compositor)
current_sequence().restack_compositors()
compositeeditor.set_compositor(self.compositor)
def _delete_compositor_redo(self):
# Compositors are recreated continually in sequnece.restack_compositors() and cannot be identified for undo/redo using object identity
# so these ids must be preserved for all succesive versions of a compositor.
if self.first_do == True:
self.destroy_id = self.compositor.destroy_id
self.first_do = False
else:
self.compositor = current_sequence().get_compositor_for_destroy_id(self.destroy_id)
current_sequence().remove_compositor(self.compositor)
current_sequence().restack_compositors()
compositeeditor.maybe_clear_editor(self.compositor)
#--------------------------------------------------- MOVE COMPOSITOR
# "compositor","clip_in","clip_out"
def move_compositor_action(data):
action = EditAction(_move_compositor_undo, _move_compositor_redo, data)
action.first_do = True
return action
def _move_compositor_undo(self):
move_compositor = current_sequence().get_compositor_for_destroy_id(self.destroy_id)
move_compositor.set_in_and_out(self.orig_in, self.orig_out)
compositeeditor.set_compositor(self.compositor) # This is different to updating e.g filter kfeditors, those are done in EditAction._update_gui()
def _move_compositor_redo(self):
# Compositors are recreated continually in sequence.restack_compositors() and cannot be identified for undo/redo using object identity
# so these ids must be preserved for all succesive versions of a compositor.
if self.first_do == True:
self.destroy_id = self.compositor.destroy_id
self.orig_in = self.compositor.clip_in
self.orig_out = self.compositor.clip_out
self.first_do = False
move_compositor = current_sequence().get_compositor_for_destroy_id(self.destroy_id)
move_compositor.set_in_and_out(self.clip_in, self.clip_out)
compositeeditor.set_compositor(self.compositor) # This is different to updating e.g filter kfeditors, those are done in EditAction._update_gui()
#----------------- AUDIO SPLICE
# "parent_clip", "audio_clip", "track"
def audio_splice_action(data):
action = EditAction(_audio_splice_undo, _audio_splice_redo, data)
return action
def _audio_splice_undo(self):
to_track = self.to_track
# Remove add audio clip
in_index = to_track.get_clip_index_at(self.over_in)
_remove_clip(to_track, in_index)
# Fix in clip and remove cut created clip if in was cut
if self.in_clip_out != -1:
in_clip = _remove_clip(to_track, in_index - 1)
_insert_clip(to_track, in_clip, in_index - 1,
in_clip.clip_in, self.in_clip_out)
self.removed_clips.pop(0)
# Fix out clip and remove cut created clip if out was cut
if self.out_clip_in != -1:
# If moved clip/s were last in the track and were moved slightly
# forward and were still last in track after move
# this leaves a trailing black that has been removed and this will fail
try:
out_clip = _remove_clip(to_track, in_index)
if len(self.removed_clips) > 0: # If overwrite was done inside single clip everything is already in order
_insert_clip(to_track, out_clip, in_index,
self.out_clip_in, out_clip.clip_out)
self.removed_clips.pop(-1)
except:
pass
# Put back old clips
for i in range(0, len(self.removed_clips)):
clip = self.removed_clips[i];
_insert_clip(to_track, clip, in_index + i, clip.clip_in,
clip.clip_out)
_do_clip_unmute(self.parent_clip)
#_remove_trailing_blanks(to_track)
def _audio_splice_redo(self):
# Get shorter name for readability
to_track = self.to_track
# Find out if overwrite starts after track end and pad track with blanck if so.
if self.over_in >= to_track.get_length():
self.starts_after_end = True
gap = self.over_out - to_track.get_length()
_insert_blank(to_track, len(to_track.clips), gap)
else:
self.starts_after_end = False
# Cut at in frame of overwrite range.
clip_in, clip_out = _overwrite_cut_track(to_track, self.over_in)
self.in_clip_out = clip_out
# Cut at out frame of overwrite range
if to_track.get_length() > self.over_out:
clip_in, clip_out = _overwrite_cut_track(to_track, self.over_out)
self.out_clip_in = clip_in
else:
self.out_clip_in = -1
# Splice out clips in overwrite range
self.removed_clips = []
in_index = to_track.get_clip_index_at(self.over_in)
out_index = to_track.get_clip_index_at(self.over_out)
for i in range(in_index, out_index):
self.removed_clips.append(_remove_clip(to_track, in_index))
# Insert audio clip
_insert_clip(to_track, self.audio_clip, in_index, self.parent_clip.clip_in, self.parent_clip.clip_out)
filter = _create_mute_volume_filter(current_sequence())
_do_clip_mute(self.parent_clip, filter)
# ------------------------------------------------- RESYNC ALL
# No input data
def resync_all_action(data):
action = EditAction(_resync_all_undo, _resync_all_redo, data)
return action
def _resync_all_undo(self):
self.actions.reverse()
for action in self.actions:
action.undo_func(action)
self.actions.reverse()
def _resync_all_redo(self):
if hasattr(self, "actions"):
# Actions have already been created, this is redo
for action in self.actions:
action.redo_func(action)
return
resync_data = resync.get_resync_data_list()
self.actions = _create_and_do_sync_actions_list(resync_data)
# ------------------------------------------------- RESYNC SOME CLIPS
# "clips"
def resync_some_clips_action(data):
action = EditAction(_resync_some_clips_undo, _resync_some_clips_redo, data)
return action
def _resync_some_clips_undo(self):
self.actions.reverse()
for action in self.actions:
action.undo_func(action)
self.actions.reverse()
def _resync_some_clips_redo(self):
if hasattr(self, "actions"):
# Actions have already been created, this is redo
for action in self.actions:
action.redo_func(action)
return
resync_data = resync.get_resync_data_list_for_clip_list(self.clips)
self.actions = _create_and_do_sync_actions_list(resync_data)
def _create_and_do_sync_actions_list(resync_data_list):
# input is list tuples list (clip, track, index, pos_off)
actions = []
for clip_data in resync_data_list:
clip, track, index, pos_offset = clip_data
# If we're in sync, do nothing
if pos_offset == clip.sync_data.pos_offset:
continue
# Get new in and out frames for clip
diff = pos_offset - clip.sync_data.pos_offset
over_in = track.clip_start(index) - diff
over_out = over_in + (clip.clip_out - clip.clip_in + 1)
data = {"track":track,
"over_in":over_in,
"over_out":over_out,
"selected_range_in":index,
"selected_range_out":index,
"move_edit_done_func":None}
action = overwrite_move_action(data)
actions.append(action)
action.redo_func(action)
return actions
# ------------------------------------------------- RESYNC CLIP SEQUENCE
# "clips"
def resync_clips_sequence_action(data):
action = EditAction(_resync_clips_sequence_undo, _resync_clips_sequence_redo, data)
return action
def _resync_clips_sequence_undo(self):
if self.sync_action != None:
self.sync_action.undo_func(self.sync_action)
def _resync_clips_sequence_redo(self):
resync_data = resync.get_resync_data_list_for_clip_list(self.clips)
clip, track, index, pos_offset = resync_data[0]
# If we're in sync, do nothing
if pos_offset == clip.sync_data.pos_offset:
self.sync_action = None
else:
# Get new in and out frames for clips
diff = pos_offset - clip.sync_data.pos_offset
over_in = track.clip_start(index) - diff
clip_last, track, index_last, pos_offset = resync_data[-1]
last_over_in = track.clip_start(index_last) - diff
over_out = last_over_in + (clip_last.clip_out - clip_last.clip_in + 1)
# Create, do and sacve edit action.
data = {"track":track,
"over_in":over_in,
"over_out":over_out,
"selected_range_in":index,
"selected_range_out":index_last,
"move_edit_done_func":None}
action = overwrite_move_action(data)
action.redo_func(action)
self.sync_action = action
# ------------------------------------------------- SET SYNC
# "child_index","child_track","parent_index","parent_track"
def set_sync_action(data):
action = EditAction(_set_sync_undo, _set_sync_redo, data)
return action
def _set_sync_undo(self):
# Get clips
child_clip = self.child_track.clips[self.child_index]
# Clear child sync data
child_clip.sync_data = None
# Clear resync data
resync.clip_sync_cleared(child_clip)
def _set_sync_redo(self):
# Get clips
child_clip = self.child_track.clips[self.child_index]
parent_clip = get_track(current_sequence().first_video_index).clips[self.parent_index]
# Get offset
child_clip_start = self.child_track.clip_start(self.child_index) - child_clip.clip_in
parent_clip_start = self.parent_track.clip_start(self.parent_index) - parent_clip.clip_in
pos_offset = child_clip_start - parent_clip_start
# Set sync data
child_clip.sync_data = SyncData()
child_clip.sync_data.pos_offset = pos_offset
child_clip.sync_data.master_clip = parent_clip
child_clip.sync_data.sync_state = appconsts.SYNC_CORRECT
resync.clip_added_to_timeline(child_clip, self.child_track)
# ------------------------------------------------- CLEAR SYNC
# "child_clip","child_track"
def clear_sync_action(data):
action = EditAction(_clear_sync_undo, _clear_sync_redo, data)
return action
def _clear_sync_undo(self):
# Reset child sync data
self.child_clip.sync_data = self.sync_data
# Save data resync data for doing resyncs and sync state gui updates
resync.clip_added_to_timeline(self.child_clip, self.child_track)
def _clear_sync_redo(self):
# Save sync data
self.sync_data = self.child_clip.sync_data
# Clear child sync data
self.child_clip.sync_data = None
# Claer resync data
resync.clip_sync_cleared(self.child_clip)
# --------------------------------------- MUTE CLIP
# "clip"
def mute_clip(data):
action = EditAction(_mute_clip_undo,_mute_clip_redo, data)
return action
def _mute_clip_undo(self):
_do_clip_unmute(self.clip)
def _mute_clip_redo(self):
mute_filter = _create_mute_volume_filter(current_sequence())
_do_clip_mute(self.clip, mute_filter)
# --------------------------------------- UNMUTE CLIP
# "clip"
def unmute_clip(data):
action = EditAction(_unmute_clip_undo,_unmute_clip_redo, data)
return action
def _unmute_clip_undo(self):
mute_filter = _create_mute_volume_filter(current_sequence())
_do_clip_mute(self.clip, mute_filter)
def _unmute_clip_redo(self):
_do_clip_unmute(self.clip)
# ----------------------------------------- TRIM END OVER BLANKS
#"track","clip","clip_index"
def trim_end_over_blanks(data):
action = EditAction(_trim_end_over_blanks_undo, _trim_end_over_blanks_redo, data)
action.exit_active_trimmode_on_edit = False
action.update_hidden_track_blank = False
return action
def _trim_end_over_blanks_undo(self):
# put back blanks
total_length = 0
for i in range(0, len(self.removed_lengths)):
length = self.removed_lengths[i]
_insert_blank(self.track, self.clip_index + 1 + i, length)
total_length = total_length + length
# trim clip
_remove_clip(self.track, self.clip_index)
_insert_clip(self.track, self.clip, self.clip_index, self.clip.clip_in, self.clip.clip_out - total_length)
def _trim_end_over_blanks_redo(self):
# Remove blanks
self.removed_lengths = _remove_consecutive_blanks(self.track, self.clip_index + 1) # +1, we're streching clip over blank are starting at NEXT index
total_length = 0
for length in self.removed_lengths:
total_length = total_length + length
# trim clip
_remove_clip(self.track, self.clip_index)
_insert_clip(self.track, self.clip, self.clip_index, self.clip.clip_in, self.clip.clip_out + total_length)
# ----------------------------------------- TRIM START OVER BLANKS
# "track","clip","blank_index"
def trim_start_over_blanks(data):
action = EditAction(_trim_start_over_blanks_undo, _trim_start_over_blanks_redo, data)
action.exit_active_trimmode_on_edit = False
action.update_hidden_track_blank = False
return action
def _trim_start_over_blanks_undo(self):
# trim clip
_remove_clip(self.track, self.blank_index)
_insert_clip(self.track, self.clip, self.blank_index, self.clip.clip_in + self.total_length, self.clip.clip_out)
# put back blanks
for i in range(0, len(self.removed_lengths)):
length = self.removed_lengths[i]
_insert_blank(self.track, self.blank_index + i, length)
def _trim_start_over_blanks_redo(self):
# Remove blanks
self.removed_lengths = _remove_consecutive_blanks(self.track, self.blank_index)
self.total_length = 0
for length in self.removed_lengths:
self.total_length = self.total_length + length
# trim clip
_remove_clip(self.track, self.blank_index)
_insert_clip(self.track, self.clip, self.blank_index, self.clip.clip_in - self.total_length, self.clip.clip_out)
# ----------------------------------------- CLIP DROPPED AFTER TRACK END APPEND
#"track","clip","blank_length", "index","clip_in", "clip_out"
def dnd_after_track_end_action(data):
action = EditAction(_dnd_after_track_end_undo, _dnd_after_track_end_redo, data)
return action
def _dnd_after_track_end_undo(self):
_remove_clip(self.track, self.index)
_remove_clip(self.track, self.index)
def _dnd_after_track_end_redo(self):
_insert_blank(self.track, self.index, self.blank_length)
_insert_clip(self.track, self.clip, self.index + 1, self.clip_in, self.clip_out)
# ----------------------------------------- CLIP DROPPED ON START PART OF BLANK
# "track","clip","blank_length","index","clip_in","clip_out"
def dnd_on_blank_start_action(data):
action = EditAction(_dnd_on_blank_start_undo, _dnd_on_blank_start_redo, data)
return action
def _dnd_on_blank_start_undo(self):
_remove_clip(self.track, self.index)
_remove_clip(self.track, self.index)
_insert_blank(self.track, self.index, self.blank_length)
def _dnd_on_blank_start_redo(self):
_remove_clip(self.track, self.index)
_insert_clip(self.track, self.clip, self.index,
self.clip_in, self.clip_out)
last_blank_length = self.blank_length - (self.clip_out - self.clip_in + 1)
_insert_blank(self.track, self.index + 1, last_blank_length)
# ----------------------------------------- CLIP DROPPED ON END PART OF BLANK
# "track","clip","overwritten_blank_length","blank_length","index","clip_in","clip_out"
def dnd_on_blank_end_action(data):
action = EditAction(_dnd_on_blank_end_undo, _dnd_on_blank_end_redo, data)
return action
def _dnd_on_blank_end_undo(self):
_remove_clip(self.track, self.index)
_remove_clip(self.track, self.index)
_insert_blank(self.track, self.index, self.blank_length)
def _dnd_on_blank_end_redo(self):
_remove_clip(self.track, self.index)
_insert_blank(self.track, self.index, self.overwritten_blank_length)
clip_length = self.blank_length - self.overwritten_blank_length - 1
_insert_clip(self.track, self.clip, self.index + 1,
self.clip_in, self.clip_in + clip_length)
# ----------------------------------------- CLIP DROPPED ON MIDDLE OF BLANK
# "track","clip","overwritten_start_frame","blank_length","index","clip_in","clip_out"
def dnd_on_blank_middle_action(data):
action = EditAction(_dnd_on_blank_middle_undo, _dnd_on_blank_middle_redo, data)
return action
def _dnd_on_blank_middle_undo(self):
_remove_clip(self.track, self.index)
_remove_clip(self.track, self.index)
_remove_clip(self.track, self.index)
_insert_blank(self.track, self.index, self.blank_length)
def _dnd_on_blank_middle_redo(self):
_remove_clip(self.track, self.index)
_insert_blank(self.track, self.index, self.overwritten_start_frame)
_insert_clip(self.track, self.clip, self.index + 1,
self.clip_in, self.clip_out)
last_blank_length = self.blank_length - self.overwritten_start_frame - (self.clip_out - self.clip_in + 1)
_insert_blank(self.track, self.index + 2, last_blank_length)
# ----------------------------------------- CLIP DROPPED TO REPLACE FULL BLANK LENGTH
# "track","clip","blank_length","index","clip_in"
def dnd_on_blank_replace_action(data):
action = EditAction(_dnd_on_blank_replace_undo, _dnd_on_blank_replace_redo, data)
return action
def _dnd_on_blank_replace_undo(self):
_remove_clip(self.track, self.index)
_insert_blank(self.track, self.index, self.blank_length)
def _dnd_on_blank_replace_redo(self):
_remove_clip(self.track, self.index)
_insert_clip(self.track, self.clip, self.index,
self.clip_in, self.clip_in + self.blank_length - 1)
# ---------------------------------------- CONSOLIDATE SELECTED BLANKS
# "track","index"
def consolidate_selected_blanks(data):
action = EditAction(_consolidate_selected_blanks_undo,_consolidate_selected_blanks_redo, data)
return action
def _consolidate_selected_blanks_undo(self):
_remove_clip(self.track, self.index)
for i in range(0, len(self.removed_lengths)):
length = self.removed_lengths[i]
_insert_blank(self.track, self.index + i, length)
def _consolidate_selected_blanks_redo(self):
self.removed_lengths = _remove_consecutive_blanks(self.track, self.index)
total_length = 0
for length in self.removed_lengths:
total_length = total_length + length
_insert_blank(self.track, self.index, total_length)
#----------------------------------- CONSOLIDATE ALL BLANKS
def consolidate_all_blanks(data):
action = EditAction(_consolidate_all_blanks_undo,_consolidate_all_blanks_redo, data)
return action
def _consolidate_all_blanks_undo(self):
self.consolidate_actions.reverse()
for c_action in self.consolidate_actions:
track, index, removed_lengths = c_action
_remove_clip(track, index)
for i in range(0, len(removed_lengths)):
length = removed_lengths[i]
_insert_blank(track, index + i, length)
def _consolidate_all_blanks_redo(self):
self.consolidate_actions = []
for i in range(1, len(current_sequence().tracks) - 1): # -1 because hidden track, 1 because black track
track = current_sequence().tracks[i]
consolidaded_indexes = []
try_do_next = True
while(try_do_next == True):
if len(track.clips) == 0:
try_do_next = False
for i in range(0, len(track.clips)):
if i == len(track.clips) - 1:
try_do_next = False
clip = track.clips[i]
if clip.is_blanck_clip == False:
continue
try:
consolidaded_indexes.index(i)
continue
except:
pass
# Now consolidate from clip in index i
consolidaded_indexes.append(i)
removed_lengths = _remove_consecutive_blanks(track, i)
total_length = 0
for length in removed_lengths:
total_length = total_length + length
_insert_blank(track, i, total_length)
self.consolidate_actions.append((track, i, removed_lengths))
break
#----------------- RANGE OVERWRITE
# "track","clip","clip_in","clip_out","mark_in_frame","mark_out_frame"
def range_overwrite_action(data):
action = EditAction(_range_over_undo, _range_over_redo, data)
return action
def _range_over_undo(self):
_remove_clip(self.track, self.track_extract_data.in_index)
_track_put_back_range(self.mark_in_frame,
self.track,
self.track_extract_data)
def _range_over_redo(self):
self.track_extract_data = _track_extract_range(self.mark_in_frame,
self.mark_out_frame,
self.track)
_insert_clip(self.track,
self.clip,
self.track_extract_data.in_index,
self.clip_in,
self.clip_out)
# HACK, see EditAction for details
self.turn_on_stop_for_edit = True
#----------------- RANGE DELETE
# "tracks","mark_in_frame","mark_out_frame"
def range_delete_action(data):
action = EditAction(_range_delete_undo, _range_delete_redo, data)
action.stop_for_edit = True
return action
def _range_delete_undo(self):
for i in range(0, len(self.tracks)): # -1 because hidden track, 1 because black track
track = self.tracks[i]
track_extract_data = self.tracks_extract_data[i]
_track_put_back_range(self.mark_in_frame,
track,
track_extract_data)
def _range_delete_redo(self):
self.tracks_extract_data = []
for track in self.tracks: # -1 because hidden track, 1 because black track
track_extracted = _track_extract_range(self.mark_in_frame,
self.mark_out_frame,
track)
self.tracks_extract_data.append(track_extracted)
# HACK, see EditAction for details
self.turn_on_stop_for_edit = True
#----------------- RIPPLE DELETE
# "track","from_index","to_index"
def ripple_delete_action(data):
action = EditAction(_ripple_delete_undo, _ripple_delete_redo, data)
action.stop_for_edit = True
return action
def _ripple_delete_undo(self):
_multi_move_undo(self)
_lift_multiple_undo(self)
def _ripple_delete_redo(self):
_lift_multiple_redo(self)
_multi_move_redo(self)
#------------------- ADD CENTERED TRANSITION
# "transition_clip","transition_index", "from_clip","to_clip","track","from_in","to_out"
def add_centered_transition_action(data):
action = EditAction(_add_centered_transition_undo, _add_centered_transition_redo, data)
return action
def _add_centered_transition_undo(self):
index = self.transition_index
track = self.track
from_clip = self.from_clip
to_clip = self.to_clip
for i in range(0, 3): # from, trans, to
_remove_clip(track, index - 1)
_insert_clip(track, from_clip, index - 1,
from_clip.clip_in, self.orig_from_clip_out)
_insert_clip(track, to_clip, index,
self.orig_to_clip_in, to_clip.clip_out)
def _add_centered_transition_redo(self):
# get shorter refs
transition_clip = self.transition_clip
index = self.transition_index
track = self.track
from_clip = self.from_clip
to_clip = self.to_clip
# Save from and to clip in/out points before adding transition
self.orig_from_clip_out = from_clip.clip_out
self.orig_to_clip_in = to_clip.clip_in
# Shorten from clip
_remove_clip(track, index - 1)
_insert_clip(track, from_clip, index - 1,
from_clip.clip_in, self.from_in) # self.from_in == transition start on from clip
# Shorten to clip
_remove_clip(track, index)
_insert_clip(track, to_clip, index,
self.to_out + 1, to_clip.clip_out) # self.to_out == transition end on to clip
# + 1 == because frame is part of inserted transition
# Insert transition
_insert_clip(track, transition_clip,
self.transition_index, 1, # first frame is dropped as it is 100% from clip
transition_clip.get_length() - 1)
#------------------- REPLACE CENTERED TRANSITION
# "track", "transition_clip","transition_index"
def replace_centered_transition_action(data):
action = EditAction(_replace_centered_transition_undo, _replace_centered_transition_redo, data)
return action
def _replace_centered_transition_undo(self):
# Remove new
_remove_clip(self.track, self.transition_index)
# Insert old
_insert_clip(self.track, self.removed_clip,
self.transition_index, 1, # first frame is dropped as it is 100% from clip
self.removed_clip.clip_out)
def _replace_centered_transition_redo(self):
# Remove old
self.removed_clip = _remove_clip(self.track, self.transition_index)
# Insert new
_insert_clip(self.track, self.transition_clip,
self.transition_index, 1, # first frame is dropped as it is 100% from clip
self.transition_clip.clip_out)
# -------------------------------------------------------- REPLACE RENDERED FADE
# "fade_clip", "index", "track", "length"
def replace_rendered_fade_action(data):
action = EditAction(_replace_rendered_fade_undo, _replace_rendered_fade_redo, data)
return action
def _replace_rendered_fade_undo(self):
_remove_clip(self.track, self.index)
_insert_clip(self.track, self.orig_fade, self.index, 0, self.length - 1)
def _replace_rendered_fade_redo(self):
self.orig_fade = _remove_clip(self.track, self.index)
_insert_clip(self.track, self.fade_clip, self.index, 0, self.length - 1)
# -------------------------------------------------------- RENDERED FADE IN
# "fade_clip", "clip_index", "track", "length"
def add_rendered_fade_in_action(data):
action = EditAction(_add_rendered_fade_in_undo, _add_rendered_fade_in_redo, data)
return action
def _add_rendered_fade_in_undo(self):
_remove_clip(self.track, self.index)
_remove_clip(self.track, self.index)
_insert_clip(self.track, self.orig_clip, self.index, self.orig_clip_in, self.orig_clip.clip_out)
def _add_rendered_fade_in_redo(self):
self.orig_clip = _remove_clip(self.track, self.index)
self.orig_clip_in = self.orig_clip.clip_in
_insert_clip(self.track, self.fade_clip, self.index, 0, self.length - 1)
_insert_clip(self.track, self.orig_clip, self.index + 1, self.orig_clip.clip_in + self.length, self.orig_clip.clip_out)
# -------------------------------------------------------- RENDERED FADE OUT
# "fade_clip", "clip_index", "track", "length"
def add_rendered_fade_out_action(data):
action = EditAction(_add_rendered_fade_out_undo, _add_rendered_fade_out_redo, data)
return action
def _add_rendered_fade_out_undo(self):
_remove_clip(self.track, self.index)
_remove_clip(self.track, self.index)
_insert_clip(self.track, self.orig_clip, self.index, self.orig_clip.clip_in, self.orig_clip_out)
def _add_rendered_fade_out_redo(self):
self.orig_clip = _remove_clip(self.track, self.index)
self.orig_clip_out = self.orig_clip.clip_out
_insert_clip(self.track, self.orig_clip, self.index, self.orig_clip.clip_in, self.orig_clip.clip_out - self.length)
_insert_clip(self.track, self.fade_clip, self.index + 1, 0, self.length - 1)
#-------------------- APPEND MEDIA LOG
# "track","clips"
def append_media_log_action(data):
action = EditAction(_append_media_log_undo,_append_media_log_redo, data)
return action
def _append_media_log_undo(self):
for i in range(0, len(self.clips)):
_remove_clip(self.track, len(self.track.clips) - 1)
def _append_media_log_redo(self):
for i in range(0, len(self.clips)):
clip = self.clips[i]
append_clip(self.track, clip, clip.clip_in, clip.clip_out)
# --------------------------------------------- help funcs for "range over" and "range splice out" edits
def _track_put_back_range(over_in, track, track_extract_data):
# get index for first clip that was removed
moved_index = track.get_clip_index_at(over_in)
# Fix in clip and remove cut created clip if in was cut
if track_extract_data.in_clip_out != -1:
in_clip = _remove_clip(track, moved_index - 1)
if in_clip.is_blanck_clip != True:
_insert_clip(track, in_clip, moved_index - 1,
in_clip.clip_in, track_extract_data.in_clip_out)
else: # blanks can't be resized, so must put in new blank
_insert_blank(track, moved_index - 1, track_extract_data.in_clip_out - in_clip.clip_in + 1)
track_extract_data.removed_clips.pop(0)
# Fix out clip and remove cut created clip if out was cut
if track_extract_data.out_clip_in != -1:
try:
out_clip = _remove_clip(track, moved_index)
if len(track_extract_data.removed_clips) > 0: # If overwrite was done inside single clip everything is already in order
# because setting in_clip back to its original length restores original state
if out_clip.is_blanck_clip != True:
_insert_clip(track, track_extract_data.orig_out_clip, moved_index,
track_extract_data.out_clip_in, out_clip.clip_out)
else: # blanks can't be resized, so must put in new blank
_insert_blank(track, moved_index, track_extract_data.out_clip_length)
track_extract_data.removed_clips.pop(-1)
except:
# If moved clip/s were last in the track and were moved slightly
# forward and were still last in track after move
# this leaves a trailing black that has been removed and this will fail
pass
# Put back old clips
for i in range(0, len(track_extract_data.removed_clips)):
clip = track_extract_data.removed_clips[i]
_insert_clip(track, clip, moved_index + i, clip.clip_in,
clip.clip_out)
#_remove_trailing_blanks(track)
def _track_extract_range(over_in, over_out, track):
track_extract_data = utils.EmptyClass()
# Find out if overwrite starts after track end and pad track with blanck if so
if over_in >= track.get_length():
track_extract_data.starts_after_end = True
gap = over_out - track.get_length()
_insert_blank(track, len(track.clips), gap)
else:
track_extract_data.starts_after_end = False
# Cut at in point if not already on cut
clip_in, clip_out = _overwrite_cut_track(track, over_in)
track_extract_data.in_clip_out = clip_out
# Cut at out point if not already on cut
track_extract_data.orig_out_clip = None
if track.get_length() > over_out:
clip_in, clip_out = _overwrite_cut_track(track, over_out, True)
track_extract_data.out_clip_in = clip_in
track_extract_data.out_clip_length = clip_out - clip_in + 1 # Cut blank can't be reconstructed with clip_in data as it is always 0 for blank, so we use this
if clip_in != -1: # if we did cut we'll need to restore the dut out clip
# which is the original clip because
orig_index = track.get_clip_index_at(over_out - 1)
track_extract_data.orig_out_clip = track.clips[orig_index]
else:
track_extract_data.out_clip_in = -1
# Splice out clips in overwrite range
track_extract_data.removed_clips = []
track_extract_data.in_index = track.get_clip_index_at(over_in)
out_index = track.get_clip_index_at(over_out)
for i in range(track_extract_data.in_index, out_index):
removed_clip = _remove_clip(track, track_extract_data.in_index)
track_extract_data.removed_clips.append(removed_clip)
return track_extract_data
# ------------------------------------------------ SLOW/FAST MOTION
# "track","clip","clip_index","speed":speed}
def replace_with_speed_changed_clip(data):
action = EditAction(_replace_with_speed_changed_clip_undo, _replace_with_speed_changed_clip_redo, data)
return action
def _replace_with_speed_changed_clip_undo(self):
pass
def _replace_with_speed_changed_clip_redo(self):
# Create slowmo clip if it does not exists
if not hasattr(self, "new_clip"):
self.new_clip = current_sequence().create_slowmotion_producer(self.clip.path, self.speed)
current_sequence().clone_clip_and_filters(self.clip, self.new_clip)
_remove_clip(self.track, self.clip_index)
_insert_clip(self.track, self.new_clip, self.clip_index, self.clip.clip_in, self.clip.clip_out)
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/editevent.py 0000664 0000000 0000000 00000062665 13610327166 0025463 0 ustar 00root root 0000000 0000000 """
Flowblade Movie Editor is a nonlinear video editor.
Copyright 2012 Janne Liljeblad.
This file is part of Flowblade Movie Editor .
Flowblade Movie Editor 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.
Flowblade Movie Editor is distributed in the hope that 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 Flowblade Movie Editor. If not, see .
"""
"""
Handles or passes on mouse edit events from timeline.
Handles edit mode setting.
"""
import os
from gi.repository import Gdk
import appconsts
import audiosync
import clipeffectseditor
import clipenddragmode
import compositeeditor
import compositormodes
import cutmode
import dialogs
import dialogutils
import edit
import editorstate
from editorstate import current_sequence
from editorstate import PLAYER
from editorstate import timeline_visible
from editorstate import EDIT_MODE
import editorpersistance
import gui
import guicomponents
import kftoolmode
import medialog
import modesetting
import movemodes
import multimovemode
import multitrimmode
import syncsplitevent
import tlinewidgets
import trimmodes
import updater
import utils
# functions are monkeypatched in at app.py
display_clip_menu_pop_up = None
compositor_menu_item_activated = None
# ----------------------------- module funcs
def do_clip_insert(track, new_clip, tline_pos):
index = _get_insert_index(track, tline_pos)
# Can't put audio media on video track
if ((new_clip.media_type == appconsts.AUDIO)
and (track.type == appconsts.VIDEO)):
_display_no_audio_on_video_msg(track)
return
movemodes.clear_selected_clips()
# Do edit
data = {"track":track,
"clip":new_clip,
"index":index,
"clip_in":new_clip.mark_in,
"clip_out":new_clip.mark_out}
action = edit.insert_action(data)
action.do_edit()
updater.display_tline_cut_frame(track, index)
def do_multiple_clip_insert(track, clips, tline_pos):
index = _get_insert_index(track, tline_pos)
# Can't put audio media on video track
for new_clip in clips:
if isinstance(new_clip, int):
continue
if ((new_clip.media_type == appconsts.AUDIO)
and (track.type == appconsts.VIDEO)):
_display_no_audio_on_video_msg(track)
return
movemodes.clear_selected_clips()
# Do edit
data = {"track":track,
"clips":clips,
"index":index}
action = edit.insert_multiple_action(data)
action.do_edit()
updater.display_tline_cut_frame(track, index)
def _attempt_dnd_overwrite(track, clip, frame):
# Can't put audio media on video track
if ((clip.media_type == appconsts.AUDIO)
and (track.type == appconsts.VIDEO)):
return
# Dropping on first available frame after last clip is append
# and is handled by insert code
if track.get_length() == frame:
return False
# Clip dropped after last clip on track
if track.get_length() < frame:
index = _get_insert_index(track, track.get_length())
movemodes.clear_selected_clips()
data = {"track":track,
"clip":clip,
"blank_length":frame - track.get_length(),
"index":index,
"clip_in":clip.mark_in,
"clip_out":clip.mark_out}
action = edit.dnd_after_track_end_action(data)
action.do_edit()
updater.display_tline_cut_frame(track, index + 1)
return True
else: # Clip dropped before end of last clip on track
index = track.get_clip_index_at(frame)
overwritten_clip = track.clips[index]
# dnd overwrites can only done on blank clips
# Drops on clips are considered inserts
if overwritten_clip.is_blanck_clip == False:
return False
drop_length = clip.mark_out - clip.mark_in + 1 # +1 , mark out incl.
blank_start = track.clip_start(index)
blank_end = track.clip_start(index + 1)
movemodes.clear_selected_clips()
# Clip dropped on first frame of blank
if blank_start == frame:
# If dropped clip longer then blank, replace blank
if frame + drop_length >= blank_end:
data = {"track":track,
"clip":clip,
"blank_length":blank_end - blank_start,
"index":index,
"clip_in":clip.mark_in}
action = edit.dnd_on_blank_replace_action(data)
action.do_edit()
else: # If dropped clip shorter then blank, replace start part of blank
data = {"track":track,
"clip":clip,
"blank_length":blank_end - blank_start,
"index":index,
"clip_in":clip.mark_in,
"clip_out":clip.mark_out}
action = edit.dnd_on_blank_start_action(data)
action.do_edit()
updater.display_tline_cut_frame(track, index)
return True
# Clip dropped after first frame of blank
if frame + drop_length >= blank_end:
# Overwrite end half of blank
data = {"track":track,
"clip":clip,
"overwritten_blank_length":frame - blank_start,
"blank_length":blank_end - blank_start,
"index":index,
"clip_in":clip.mark_in,
"clip_out":clip.mark_out}
action = edit.dnd_on_blank_end_action(data)
action.do_edit()
else: # Overwrite part of blank ei toimi
data = {"track":track,
"clip":clip,
"overwritten_start_frame":frame - blank_start,
"blank_length":blank_end - blank_start,
"index":index,
"clip_in":clip.mark_in,
"clip_out":clip.mark_out}
action = edit.dnd_on_blank_middle_action(data)
action.do_edit()
updater.display_tline_cut_frame(track, index + 1)
return True
return False # this won't be hit
def _get_insert_index(track, tline_pos):
cut_frame = current_sequence().get_closest_cut_frame(track.id, tline_pos)
index = current_sequence().get_clip_index(track, cut_frame)
if index == -1:
# Fix for case when inserting on empty track, which causes exception in
# editorstate.current_sequence().get_clip_index(...) which returns -1
index = track.count()
elif ((cut_frame == -1) and (index == 0)
and (tline_pos > 0) and (tline_pos >= track.get_length())):
# Fix for case in which we get -1 for cut_frame because
# tline_pos after last frame of the sequence, and
# then get 0 for index which places clip in beginning, but we
# want it appended in the end of sequence.
index = track.count()
return index
def _display_no_audio_on_video_msg(track):
dialogs.no_audio_dialog(track)
# ------------------------------------ timeline mouse events
def tline_canvas_mouse_pressed(event, frame):
"""
Mouse event callback from timeline canvas widget
"""
editorstate.timeline_mouse_disabled = False # This is used to disable "move and "release" events when they would get bad data.
if PLAYER().looping():
return
elif PLAYER().is_playing():
PLAYER().stop_playback()
# Double click handled separately
if event.type == Gdk.EventType._2BUTTON_PRESS:
return
# Handle and exit parent clip selecting
if EDIT_MODE() == editorstate.SELECT_PARENT_CLIP:
syncsplitevent.select_sync_parent_mouse_pressed(event, frame)
editorstate.timeline_mouse_disabled = True
# Set INSERT_MODE
modesetting.set_default_edit_mode()
return
# Handle and exit tline sync clip selecting
if EDIT_MODE() == editorstate.SELECT_TLINE_SYNC_CLIP:
audiosync.select_sync_clip_mouse_pressed(event, frame)
editorstate.timeline_mouse_disabled = True
# Set INSERT_MODE
modesetting.set_default_edit_mode()
return
# Hitting timeline in clip display mode displays timeline in
# default mode.
if not timeline_visible():
updater.display_sequence_in_monitor()
if (event.button == 1):
# Now that we have correct edit mode we'll reenter
# this method to get e.g. a select action
tline_canvas_mouse_pressed(event, frame)
return
if (event.button == 3):
# Right mouse + CTRL displays clip menu if we hit clip
if (event.get_state() & Gdk.ModifierType.CONTROL_MASK):
PLAYER().seek_frame(frame)
# Right mouse on timeline seeks frame
else:
success = display_clip_menu_pop_up(event.y, event, frame)
if not success:
PLAYER().seek_frame(frame)
return
# If clip end drag mode is for some reason still active, exit to default edit mode
if EDIT_MODE() == editorstate.CLIP_END_DRAG:
modesetting.set_default_edit_mode()
# This shouldn't happen unless for some reason mouse release didn't hit clipenddragmode listener.
print("EDIT_MODE() == editorstate.CLIP_END_DRAG at mouse press!")
# Check if match frame close is hit
if editorstate.current_is_move_mode() and timeline_visible():
if tlinewidgets.match_frame_close_hit(event.x, event.y) == True:
tlinewidgets.set_match_frame(-1, -1, True)
updater.repaint_tline()
return
# Check if compositor is hit and if so, handle compositor editing
if editorstate.current_is_move_mode() and timeline_visible():
hit_compositor = tlinewidgets.compositor_hit(frame, event.x, event.y, current_sequence().compositors)
if hit_compositor != None:
if editorstate.get_compositing_mode() == appconsts.COMPOSITING_MODE_STANDARD_AUTO_FOLLOW:
compositeeditor.set_compositor(hit_compositor)
compositormodes.set_compositor_selected(hit_compositor)
movemodes.clear_selected_clips()
editorstate.timeline_mouse_disabled = True
return
elif editorstate.auto_follow_active() == False or hit_compositor.obey_autofollow == False:
movemodes.clear_selected_clips()
if event.button == 1 or (event.button == 3 and event.get_state() & Gdk.ModifierType.CONTROL_MASK):
compositormodes.set_compositor_mode(hit_compositor)
mode_funcs = EDIT_MODE_FUNCS[editorstate.COMPOSITOR_EDIT]
press_func = mode_funcs[TL_MOUSE_PRESS]
press_func(event, frame)
return
if event.button == 3:
compositormodes.set_compositor_selected(hit_compositor)
guicomponents.display_compositor_popup_menu(event, hit_compositor,
compositor_menu_item_activated)
return
elif event.button == 2:
updater.zoom_project_length()
return
compositormodes.clear_compositor_selection()
# Check if we should enter clip end drag mode
if (event.button == 3 and editorstate.current_is_move_mode()
and timeline_visible() and (event.get_state() & Gdk.ModifierType.CONTROL_MASK)):
# with CTRL right mouse
clipenddragmode.maybe_init_for_mouse_press(event, frame)
elif (timeline_visible() and (EDIT_MODE() == editorstate.INSERT_MOVE or EDIT_MODE() == editorstate.OVERWRITE_MOVE)
and (tlinewidgets.pointer_context == appconsts.POINTER_CONTEXT_END_DRAG_LEFT or tlinewidgets.pointer_context == appconsts.POINTER_CONTEXT_END_DRAG_RIGHT)):
# with pointer context
clipenddragmode.maybe_init_for_mouse_press(event, frame)
# Handle mouse button presses depending which button was pressed and
# editor state.
# RIGHT BUTTON: seek frame or display clip menu if not dragging clip end
if (event.button == 3 and EDIT_MODE() != editorstate.CLIP_END_DRAG and EDIT_MODE() != editorstate.KF_TOOL):
if ((not editorstate.current_is_active_trim_mode()) and timeline_visible()):
if not(event.get_state() & Gdk.ModifierType.CONTROL_MASK):
success = display_clip_menu_pop_up(event.y, event, frame)
if not success:
PLAYER().seek_frame(frame)
else:
# For trim modes set _NO_EDIT edit mode and seek frame. and seek frame
trimmodes.set_no_edit_trim_mode()
PLAYER().seek_frame(frame)
return
# LEFT BUTTON: Select new trimmed clip in active one roll trim mode with sensitive cursor.
elif (event.button == 1 and EDIT_MODE() == editorstate.ONE_ROLL_TRIM):
track = tlinewidgets.get_track(event.y)
if track == None:
modesetting.set_default_edit_mode(True)
return
success = trimmodes.set_oneroll_mode(track, frame)
if not success:
modesetting.set_default_edit_mode(True)
return
if trimmodes.edit_data["to_side_being_edited"] == True:
pointer_context = appconsts.POINTER_CONTEXT_TRIM_LEFT
else:
pointer_context = appconsts.POINTER_CONTEXT_TRIM_RIGHT
gui.editor_window.set_tline_cursor_to_context(pointer_context)
gui.editor_window.set_tool_selector_to_mode()
if not editorpersistance.prefs.quick_enter_trims:
editorstate.timeline_mouse_disabled = True
else:
trimmodes.oneroll_trim_move(event.x, event.y, frame, None)
elif event.button == 2:
updater.zoom_project_length()
# LEFT BUTTON: Handle left mouse button edits by passing event to current edit mode
# handler func
elif event.button == 1 or event.button == 3:
mode_funcs = EDIT_MODE_FUNCS[EDIT_MODE()]
press_func = mode_funcs[TL_MOUSE_PRESS]
press_func(event, frame)
def tline_canvas_mouse_moved(x, y, frame, button, state):
"""
Mouse event callback from timeline canvas widget
"""
# Refuse mouse events for some editor states.
if PLAYER().looping():
return
if editorstate.timeline_mouse_disabled == True:
return
if not timeline_visible():
return
# Handle timeline position setting with right mouse button
if button == 3 and EDIT_MODE() != editorstate.CLIP_END_DRAG and EDIT_MODE() != editorstate.COMPOSITOR_EDIT and EDIT_MODE() != editorstate.KF_TOOL:
if not timeline_visible():
return
PLAYER().seek_frame(frame)
# Handle mouse button edits
elif button == 1 or button == 3:
mode_funcs = EDIT_MODE_FUNCS[EDIT_MODE()]
move_func = mode_funcs[TL_MOUSE_MOVE]
move_func(x, y, frame, state)
def tline_canvas_mouse_released(x, y, frame, button, state):
"""
Mouse event callback from timeline canvas widget
"""
gui.editor_window.set_cursor_to_mode() # we need this for box move at least, probably trims too
if editorstate.timeline_mouse_disabled == True:
gui.editor_window.set_cursor_to_mode() # we only need this update when mode change (to active trim mode) disables mouse, so we'll only do this then
tlinewidgets.trim_mode_in_non_active_state = False # we only need this update when mode change (to active trim mode) disables mouse, so we'll only do this then
gui.tline_canvas.widget.queue_draw()
editorstate.timeline_mouse_disabled = False
return
if not timeline_visible():
return
if PLAYER().looping():
PLAYER().stop_loop_playback(trimmodes.trim_looping_stopped)
return
# Handle timeline position setting with right mouse button
if button == 3 and EDIT_MODE() != editorstate.CLIP_END_DRAG and EDIT_MODE() != editorstate.COMPOSITOR_EDIT and EDIT_MODE() != editorstate.KF_TOOL:
if not timeline_visible():
return
PLAYER().seek_frame(frame)
# Handle mouse button edits
elif button == 1 or button == 3:
mode_funcs = EDIT_MODE_FUNCS[EDIT_MODE()]
release_func = mode_funcs[TL_MOUSE_RELEASE]
release_func(x, y, frame, state)
def tline_canvas_double_click(frame, x, y):
if PLAYER().looping():
return
elif PLAYER().is_playing():
PLAYER().stop_playback()
if not timeline_visible():
updater.display_sequence_in_monitor()
modesetting.set_default_edit_mode()
return
hit_compositor = tlinewidgets.compositor_hit(frame, x, y, current_sequence().compositors)
if hit_compositor != None:
compositeeditor.set_compositor(hit_compositor)
return
track = tlinewidgets.get_track(y)
if track == None:
return
clip_index = current_sequence().get_clip_index(track, frame)
if clip_index == -1:
return
clip = track.clips[clip_index]
if clip.is_blanck_clip == True:
return
data = (clip, track, None, x)
updater.open_clip_in_effects_editor(data)
# -------------------------------------------------- DND release event callbacks
def tline_effect_drop(x, y):
clip, track, index = tlinewidgets.get_clip_track_and_index_for_pos(x, y)
if clip == None:
return
if track == None:
return
if track.id < 1 or track.id >= (len(current_sequence().tracks) - 1):
return
if dialogutils.track_lock_check_and_user_info(track):
modesetting.set_default_edit_mode()
return
if clip != clipeffectseditor.clip:
clipeffectseditor.set_clip(clip, track, index)
clipeffectseditor.add_currently_selected_effect() # drag start selects the dragged effect
def tline_media_drop(media_file, x, y, use_marks=False):
track = tlinewidgets.get_track(y)
if track == None:
return
if track.id < 1 or track.id >= (len(current_sequence().tracks) - 1):
return
if dialogutils.track_lock_check_and_user_info(track):
#modesetting.set_default_edit_mode()
# TODO: Info
return
modesetting.stop_looping()
if EDIT_MODE() == editorstate.KF_TOOL:
kftoolmode.exit_tool()
frame = tlinewidgets.get_frame(x)
# Create new clip.
if media_file.type != appconsts.PATTERN_PRODUCER:
new_clip = current_sequence().create_file_producer_clip(media_file.path, media_file.name, False, media_file.ttl)
else:
new_clip = current_sequence().create_pattern_producer(media_file)
# Set clip in and out
if use_marks == False:
new_clip.mark_in = 0
new_clip.mark_out = new_clip.get_length() - 1 # - 1 because out is mark_out inclusive
if media_file.type == appconsts.IMAGE_SEQUENCE:
new_clip.mark_out = media_file.length
else:
if new_clip.media_type == appconsts.IMAGE or new_clip.media_type == appconsts.PATTERN_PRODUCER:
# Give IMAGE and PATTERN_PRODUCER media types default mark in and mark out if not already set.
# This makes them reasonably short and trimmable in both directions.
# NOTE: WE SHOULD BE DOING THIS AT CREATION TIME, WE'RE DOING THE SAME THING IN updater.display_clip_in_monitor() ?
# ...but then we would need to patch persistance.py...maybe keep this even if not too smart.
# TODO: Make default length user settable or use graphics value
if (hasattr(new_clip, 'mark_in') == False) or (new_clip.mark_in == -1 and new_clip.mark_out == -1):
center_frame = new_clip.get_length() // 2
default_length_half = 75
mark_in = center_frame - default_length_half
mark_out = center_frame + default_length_half - 1
new_clip.mark_in = mark_in
new_clip.mark_out = mark_out
else: # All the rest
new_clip.mark_in = media_file.mark_in
new_clip.mark_out = media_file.mark_out
if new_clip.mark_in == -1:
new_clip.mark_in = 0
if new_clip.mark_out == -1:
new_clip.mark_out = new_clip.get_length() - 1 # - 1 because out is mark_out inclusive
if media_file.type == appconsts.IMAGE_SEQUENCE:
new_clip.mark_out = media_file.length
# Graphics files get added with their default lengths
f_name, ext = os.path.splitext(media_file.name)
if utils.file_extension_is_graphics_file(ext) and media_file.type != appconsts.IMAGE_SEQUENCE: # image sequences are graphics files but have own length
in_fr, out_fr, l = editorpersistance.get_graphics_default_in_out_length()
new_clip.mark_in = in_fr
new_clip.mark_out = out_fr
# Non-insert DND actions
if editorpersistance.prefs.dnd_action == appconsts.DND_OVERWRITE_NON_V1:
if track.id != current_sequence().first_video_track().id:
drop_done = _attempt_dnd_overwrite(track, new_clip, frame)
if drop_done == True:
return
elif editorpersistance.prefs.dnd_action == appconsts.DND_ALWAYS_OVERWRITE:
drop_done = _attempt_dnd_overwrite(track, new_clip, frame)
if drop_done == True:
return
do_clip_insert(track, new_clip, frame)
def tline_range_item_drop(rows, x, y):
track = tlinewidgets.get_track(y)
if track == None:
return
if track.id < 1 or track.id >= (len(current_sequence().tracks) - 1):
return
if dialogutils.track_lock_check_and_user_info(track):
modesetting.set_default_edit_mode()
return
frame = tlinewidgets.get_frame(x)
clips = medialog.get_clips_for_rows(rows)
modesetting.set_default_edit_mode()
do_multiple_clip_insert(track, clips, frame)
# ------------------------------------ function tables
# mouse event indexes
TL_MOUSE_PRESS = 0
TL_MOUSE_MOVE = 1
TL_MOUSE_RELEASE = 2
# mouse event handler function lists for mode
INSERT_MOVE_FUNCS = [movemodes.insert_move_press,
movemodes.insert_move_move,
movemodes.insert_move_release]
OVERWRITE_MOVE_FUNCS = [movemodes.overwrite_move_press,
movemodes.overwrite_move_move,
movemodes.overwrite_move_release]
ONE_ROLL_TRIM_FUNCS = [trimmodes.oneroll_trim_press,
trimmodes.oneroll_trim_move,
trimmodes.oneroll_trim_release]
ONE_ROLL_TRIM_NO_EDIT_FUNCS = [modesetting.oneroll_trim_no_edit_press,
modesetting.oneroll_trim_no_edit_move,
modesetting.oneroll_trim_no_edit_release]
TWO_ROLL_TRIM_FUNCS = [trimmodes.tworoll_trim_press,
trimmodes.tworoll_trim_move,
trimmodes.tworoll_trim_release]
TWO_ROLL_TRIM_NO_EDIT_FUNCS = [modesetting.tworoll_trim_no_edit_press,
modesetting.tworoll_trim_no_edit_move,
modesetting.tworoll_trim_no_edit_release]
COMPOSITOR_EDIT_FUNCS = [compositormodes.mouse_press,
compositormodes.mouse_move,
compositormodes.mouse_release]
SLIDE_TRIM_FUNCS = [trimmodes.slide_trim_press,
trimmodes.slide_trim_move,
trimmodes.slide_trim_release]
SLIDE_TRIM_NO_EDIT_FUNCS = [modesetting.slide_trim_no_edit_press,
modesetting.slide_trim_no_edit_move,
modesetting.slide_trim_no_edit_release]
MULTI_MOVE_FUNCS = [multimovemode.mouse_press,
multimovemode.mouse_move,
multimovemode.mouse_release]
CLIP_END_DRAG_FUNCS = [clipenddragmode.mouse_press,
clipenddragmode.mouse_move,
clipenddragmode.mouse_release]
CUT_FUNCS = [cutmode.mouse_press,
cutmode.mouse_move,
cutmode.mouse_release]
KFTOOL_FUNCS = [kftoolmode.mouse_press,
kftoolmode.mouse_move,
kftoolmode.mouse_release]
MULTI_TRIM_FUNCS = [multitrimmode.mouse_press,
multitrimmode.mouse_move,
multitrimmode.mouse_release]
# (mode -> mouse handler function list) table
EDIT_MODE_FUNCS = {editorstate.INSERT_MOVE:INSERT_MOVE_FUNCS,
editorstate.OVERWRITE_MOVE:OVERWRITE_MOVE_FUNCS,
editorstate.ONE_ROLL_TRIM:ONE_ROLL_TRIM_FUNCS,
editorstate.TWO_ROLL_TRIM:TWO_ROLL_TRIM_FUNCS,
editorstate.COMPOSITOR_EDIT:COMPOSITOR_EDIT_FUNCS,
editorstate.ONE_ROLL_TRIM_NO_EDIT:ONE_ROLL_TRIM_NO_EDIT_FUNCS,
editorstate.TWO_ROLL_TRIM_NO_EDIT:TWO_ROLL_TRIM_NO_EDIT_FUNCS,
editorstate.SLIDE_TRIM:SLIDE_TRIM_FUNCS,
editorstate.SLIDE_TRIM_NO_EDIT:SLIDE_TRIM_NO_EDIT_FUNCS,
editorstate.MULTI_MOVE:MULTI_MOVE_FUNCS,
editorstate.CLIP_END_DRAG:CLIP_END_DRAG_FUNCS,
editorstate.CUT:CUT_FUNCS,
editorstate.KF_TOOL:KFTOOL_FUNCS,
editorstate.MULTI_TRIM:MULTI_TRIM_FUNCS}
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/editorpersistance.py 0000664 0000000 0000000 00000036023 13610327166 0027210 0 ustar 00root root 0000000 0000000 """
Flowblade Movie Editor is a nonlinear video editor.
Copyright 2012 Janne Liljeblad.
This file is part of Flowblade Movie Editor .
Flowblade Movie Editor 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.
Flowblade Movie Editor is distributed in the hope that 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 Flowblade Movie Editor. If not, see .
"""
"""
Module handles saving and loading data that is related to the editor and not any particular project.
"""
"""
Change History:
Aug-2019 - SvdB - AS:
Save value of Autosave preference.
See preferenceswindow.py for more info
"""
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
import os
import pickle
import appconsts
import atomicfile
import mltprofiles
import userfolders
import utils
PREFS_DOC = "prefs"
RECENT_DOC = "recent"
MAX_RECENT_PROJS = 15
UNDO_STACK_DEFAULT = 30
UNDO_STACK_MIN = 10
UNDO_STACK_MAX = 100
GLASS_STYLE = 0
SIMPLE_STYLE = 1
NO_DECORATIONS = 2
prefs = None
recent_projects = None
def load():
"""
If docs fail to load, new ones are created and saved.
"""
prefs_file_path = userfolders.get_config_dir() + PREFS_DOC
recents_file_path = userfolders.get_config_dir() + RECENT_DOC
global prefs, recent_projects
try:
prefs = utils.unpickle(prefs_file_path)
except:
prefs = EditorPreferences()
with atomicfile.AtomicFileWriter(prefs_file_path, "wb") as afw:
write_file = afw.get_file()
pickle.dump(prefs, write_file)
# Override deprecated preferences to default values.
prefs.delta_overlay = True
prefs.auto_play_in_clip_monitor = False
prefs.empty_click_exits_trims = True
prefs.quick_enter_trims = True
prefs.remember_monitor_clip_frame = True
try:
recent_projects = utils.unpickle(recents_file_path)
except:
recent_projects = utils.EmptyClass()
recent_projects.projects = []
with atomicfile.AtomicFileWriter(recents_file_path, "wb") as afw:
write_file = afw.get_file()
pickle.dump(recent_projects, write_file)
# Remove non-existing projects from recents list
remove_list = []
for proj_path in recent_projects.projects:
if os.path.isfile(proj_path) == False:
remove_list.append(proj_path)
if len(remove_list) > 0:
for proj_path in remove_list:
recent_projects.projects.remove(proj_path)
with atomicfile.AtomicFileWriter(recents_file_path, "wb") as afw:
write_file = afw.get_file()
pickle.dump(recent_projects, write_file)
# Versions of program may have different prefs objects and
# we may need to to update prefs on disk if user has e.g.
# installed later version of Flowblade.
current_prefs = EditorPreferences()
if len(prefs.__dict__) != len(current_prefs.__dict__):
current_prefs.__dict__.update(prefs.__dict__)
prefs = current_prefs
with atomicfile.AtomicFileWriter(prefs_file_path, "wb") as afw:
write_file = afw.get_file()
pickle.dump(prefs, write_file)
print("prefs updated to new version, new param count:", len(prefs.__dict__))
def save():
"""
Write out prefs and recent_projects files
"""
prefs_file_path = userfolders.get_config_dir()+ PREFS_DOC
recents_file_path = userfolders.get_config_dir() + RECENT_DOC
with atomicfile.AtomicFileWriter(prefs_file_path, "wb") as afw:
write_file = afw.get_file()
pickle.dump(prefs, write_file)
with atomicfile.AtomicFileWriter(recents_file_path, "wb") as afw:
write_file = afw.get_file()
pickle.dump(recent_projects, write_file)
def add_recent_project_path(path):
"""
Called when project is saved.
"""
if len(recent_projects.projects) == MAX_RECENT_PROJS:
recent_projects.projects.pop(-1)
# Reject autosaves.
autosave_dir = userfolders.get_cache_dir() + appconsts.AUTOSAVE_DIR
file_save_dir = os.path.dirname(path) + "/"
if file_save_dir == autosave_dir:
return
try:
index = recent_projects.projects.index(path)
recent_projects.projects.pop(index)
except:
pass
recent_projects.projects.insert(0, path)
save()
def remove_non_existing_recent_projects():
# Remove non-existing projects from recents list
recents_file_path = userfolders.get_config_dir() + RECENT_DOC
remove_list = []
for proj_path in recent_projects.projects:
if os.path.isfile(proj_path) == False:
remove_list.append(proj_path)
if len(remove_list) > 0:
for proj_path in remove_list:
recent_projects.projects.remove(proj_path)
with atomicfile.AtomicFileWriter(recents_file_path, "wb") as afw:
write_file = afw.get_file()
pickle.dump(recent_projects, write_file)
def fill_recents_menu_widget(menu_item, callback):
"""
Fills menu item with menuitems to open recent projects.
"""
menu = menu_item.get_submenu()
# Remove current items
items = menu.get_children()
for item in items:
menu.remove(item)
# Add new menu items
recent_proj_names = get_recent_projects()
if len(recent_proj_names) != 0:
for i in range (0, len(recent_proj_names)):
proj_name = recent_proj_names[i]
new_item = Gtk.MenuItem(proj_name)
new_item.connect("activate", callback, i)
menu.append(new_item)
new_item.show()
# ...or a single non-sensitive Empty item
else:
new_item = Gtk.MenuItem(_("Empty"))
new_item.set_sensitive(False)
menu.append(new_item)
new_item.show()
def get_recent_projects():
"""
Returns list of names of recent projects.
"""
proj_list = []
for proj_path in recent_projects.projects:
proj_list.append(os.path.basename(proj_path))
return proj_list
def update_prefs_from_widgets(widgets_tuples_tuple):
# Aug-2019 - SvdB - BB - Replace double_track_hights by double_track_hights
# Unpack widgets
gen_opts_widgets, edit_prefs_widgets, playback_prefs_widgets, view_prefs_widgets, performance_widgets = widgets_tuples_tuple
# Aug-2019 - SvdB - AS - added autosave_combo
default_profile_combo, open_in_last_opened_check, open_in_last_rendered_check, undo_max_spin, load_order_combo, \
autosave_combo = gen_opts_widgets
# Jul-2016 - SvdB - Added play_pause_button
# Apr-2017 - SvdB - Added ffwd / rev values
gfx_length_spin, cover_delete, mouse_scroll_action, hide_file_ext_button, \
hor_scroll_dir, effects_editor_clip_load = edit_prefs_widgets
auto_center_check, play_pause_button, auto_center_on_updown, \
ffwd_rev_shift_spin, ffwd_rev_ctrl_spin, ffwd_rev_caps_spin, follow_move_range, loop_clips = playback_prefs_widgets
force_language_combo, disp_splash, buttons_style, theme, theme_combo, audio_levels_combo, \
window_mode_combo, full_names, double_track_hights, top_row_layout, layout_monitor = view_prefs_widgets
# Jan-2017 - SvdB
perf_render_threads, perf_drop_frames = performance_widgets
global prefs
prefs.open_in_last_opended_media_dir = open_in_last_opened_check.get_active()
prefs.remember_last_render_dir = open_in_last_rendered_check.get_active()
prefs.default_profile_name = mltprofiles.get_profile_name_for_index(default_profile_combo.get_active())
prefs.undos_max = undo_max_spin.get_adjustment().get_value()
prefs.media_load_order = load_order_combo.get_active()
prefs.auto_center_on_play_stop = auto_center_check.get_active()
prefs.default_grfx_length = int(gfx_length_spin.get_adjustment().get_value())
prefs.trans_cover_delete = cover_delete.get_active()
# Jul-2016 - SvdB - For play/pause button
prefs.play_pause = play_pause_button.get_active()
prefs.hide_file_ext = hide_file_ext_button.get_active()
prefs.mouse_scroll_action_is_zoom = (mouse_scroll_action.get_active() == 0)
prefs.scroll_horizontal_dir_up_forward = (hor_scroll_dir.get_active() == 0)
prefs.single_click_effects_editor_load = (effects_editor_clip_load.get_active() == 1)
# Apr-2017 - SvdB - ffwd / rev values
prefs.ffwd_rev_shift = int(ffwd_rev_shift_spin.get_adjustment().get_value())
prefs.ffwd_rev_ctrl = int(ffwd_rev_ctrl_spin.get_adjustment().get_value())
prefs.ffwd_rev_caps = int(ffwd_rev_caps_spin.get_adjustment().get_value())
prefs.loop_clips = loop_clips.get_active()
prefs.use_english_always = False # DEPRECATED, "force_language" used instead
prefs.force_language = force_language_combo.lang_codes[force_language_combo.get_active()]
prefs.display_splash_screen = disp_splash.get_active()
prefs.buttons_style = buttons_style.get_active() # styles enum values and widget indexes correspond
prefs.theme_fallback_colors = theme_combo.get_active()
prefs.display_all_audio_levels = (audio_levels_combo.get_active() == 0)
prefs.global_layout = window_mode_combo.get_active() + 1 # +1 'cause values are 1 and 2
# Jan-2017 - SvdB
prefs.perf_render_threads = int(perf_render_threads.get_adjustment().get_value())
prefs.perf_drop_frames = perf_drop_frames.get_active()
# Feb-2017 - SvdB - for full file names
prefs.show_full_file_names = full_names.get_active()
prefs.center_on_arrow_move = auto_center_on_updown.get_active()
prefs.double_track_hights = (double_track_hights.get_active() == 1)
prefs.playback_follow_move_tline_range = follow_move_range.get_active()
prefs.theme = theme.get_active()
prefs.top_row_layout = top_row_layout.get_active()
# Aug-2019 - SvdB - AS
prefs.auto_save_delay_value_index = autosave_combo.get_active()
prefs.layout_display_index = layout_monitor.get_active()
def get_graphics_default_in_out_length():
in_fr = int(15000/2) - int(prefs.default_grfx_length/2)
out_fr = in_fr + int(prefs.default_grfx_length) - 1 # -1, out inclusive
return (in_fr, out_fr, prefs.default_grfx_length)
class EditorPreferences:
"""
Class holds data of persistant user preferences for editor.
"""
def __init__(self):
# Every preference needs to have its default value set in this constructor
self.open_in_last_opended_media_dir = True
self.last_opened_media_dir = None
self.img_length = 2000
self.auto_save_delay_value_index = 1 # value is index of AUTO_SAVE_OPTS in preferenceswindow._general_options_panel()
self.undos_max = UNDO_STACK_DEFAULT
self.default_profile_name = 10 # index of default profile
self.auto_play_in_clip_monitor = False # DEPRECATED, NOT USER SETTABLE ANYMORE
self.auto_center_on_play_stop = False
self.thumbnail_folder = None # DEPRECATED, this set XDG variables now
self.hidden_profile_names = []
self.display_splash_screen = True
self.auto_move_after_edit = False
self.default_grfx_length = 250 # value is in frames
self.track_configuration = 0 # DEPRECATED
self.AUTO_SAVE_OPTS = None # not used, these are cerated and translated else where
self.tabs_on_top = False
self.midbar_tc_left = True
self.default_layout = True
self.exit_allocation = (0, 0)
self.media_columns = 3
self.app_v_paned_position = 500 # Paned get/set position value
self.top_paned_position = 600 # Paned get/set position value
self.mm_paned_position = 260 # Paned get/set position value
self.render_folder = None # DEPRECATED, this set by XDG variables now
self.show_sequence_profile = True
self.buttons_style = GLASS_STYLE
self.dark_theme = False # DEPRECATED, "theme" used instead
self.remember_last_render_dir = True
self.empty_click_exits_trims = True # DEPRECATED, NOT USER SETTABLE ANYMORE
self.quick_enter_trims = True # DEPRECATED, NOT USER SETTABLE ANYMORE
self.show_vu_meter = True # DEPRECATED, NOT USER SETTABLE ANYMORE
self.remember_monitor_clip_frame = True # DEPRECATED, NOT USER SETTABLE ANYMORE
self.jack_start_up_op = appconsts.JACK_ON_START_UP_NO # not used
self.jack_frequency = 48000 # not used
self.jack_output_type = appconsts.JACK_OUT_AUDIO # not used
self.media_load_order = appconsts.LOAD_ABSOLUTE_FIRST
self.use_english_always = False # DEPRECATED, "force_language" used instead
self.theme_fallback_colors = 4 # index of gui._THEME_COLORS
self.display_all_audio_levels = True
self.overwrite_clip_drop = True # DEPRECATED, "dnd_action" used instead
self.trans_cover_delete = True
# Jul-2016 - SvdB - For play/pause button
self.play_pause = False
self.midbar_layout = appconsts.MIDBAR_TC_LEFT
self.global_layout = appconsts.SINGLE_WINDOW
self.trim_view_default = appconsts.TRIM_VIEW_OFF
self.trim_view_message_shown = False
self.exit_allocation_window_2 = (0, 0, 0, 0)
self.mouse_scroll_action_is_zoom = True
self.hide_file_ext = False
# Jan-2017 - SvdB
self.perf_render_threads = 1
self.perf_drop_frames = False
# Feb-2017 - SvdB - for full file names
self.show_full_file_names = False
self.center_on_arrow_move = False
# Apr-2017 - SvdB - Using these values we maintain the original hardcoded speed
self.ffwd_rev_shift = 1
self.ffwd_rev_ctrl = 10
self.ffwd_rev_caps = 1
self.shortcuts = "flowblade.xml"
self.double_track_hights = False
self.delta_overlay = True # DEPRECATED, NOT USER SETTABLE ANYMORE
self.show_alpha_info_message = True
self.playback_follow_move_tline_range = True
self.active_tools = [1, 2, 3, 4, 5, 6, 7]
self.top_level_project_panel = True
self.theme = appconsts.FLOWBLADE_THEME
self.dnd_action = appconsts.DND_OVERWRITE_NON_V1
self.top_row_layout = appconsts.THREE_PANELS_IF_POSSIBLE
self.box_for_empty_press_in_overwrite_tool = False
self.scroll_horizontal_dir_up_forward = True
self.kf_edit_init_affects_playhead = False # DEPRECATED, this feature is now removed, kf editor inits no longer have effect on playhead
self.show_tool_tooltips = True
self.workflow_dialog_last_version_shown = "0.0.1"
self.loop_clips = False
self.audio_scrubbing = False
self.force_language = "None"
self.default_compositing_mode = appconsts.COMPOSITING_MODE_TOP_DOWN_FREE_MOVE
self.single_click_effects_editor_load = False
self.layout_display_index = 0 # 0 == full area - 1,2... monitor number
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/editorstate.py 0000664 0000000 0000000 00000017643 13610327166 0026017 0 ustar 00root root 0000000 0000000 """
Flowblade Movie Editor is a nonlinear video editor.
Copyright 2012 Janne Liljeblad.
This file is part of Flowblade Movie Editor .
Flowblade Movie Editor 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.
Flowblade Movie Editor is distributed in the hope that 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 Flowblade Movie Editor. If not, see .
"""
"""
Module holds current global editor state.
Accessor methods are there mainly to improve code readability elsewhere.
We're using BIG_METHOD_NAMES() for state objects. This is a bit unusual
but looks good when reading code.
"""
import appconsts
# Edit modes
INSERT_MOVE = 0
OVERWRITE_MOVE = 1
ONE_ROLL_TRIM = 2
TWO_ROLL_TRIM = 3
SELECT_PARENT_CLIP = 4
COMPOSITOR_EDIT = 5
ONE_ROLL_TRIM_NO_EDIT = 6
TWO_ROLL_TRIM_NO_EDIT = 7
SLIDE_TRIM = 8
SLIDE_TRIM_NO_EDIT = 9
MULTI_MOVE = 10
CLIP_END_DRAG = 11
SELECT_TLINE_SYNC_CLIP = 12
CUT = 13
KF_TOOL = 14
MULTI_TRIM = 15
# SDL version (Not used currently)
SDL_1 = 1
SDL_2 = 2
# Project being edited
project = None
# Wrapped MLT framework producer->consumer media player
player = None
# Current edit mode
edit_mode = INSERT_MOVE
# Ripple Trim tool is ONE_ROLL_TRIM mode + True on this flag
trim_mode_ripple = False
# Box tool OVERWRITE_MOVE tool mode + True on this flag
overwrite_mode_box = False
# Media files view filter for selecting displayed media objects in bin
media_view_filter = appconsts.SHOW_ALL_FILES
# Media file displayed in monitor when 'Clip' is pressed
_monitor_media_file = None
# Flag for timeline/clip display in monitor
_timeline_displayed = True
# Used to ignore drag and release events when press doesn't start an action that can/should handle those events.
timeline_mouse_disabled = False
# Timeline current frame is saved here while clip is being displayed in monitor
# and PLAYER() current frame is not timeline frame
tline_shadow_frame = -1
# Dict of current proxy media paths
_current_proxy_paths = {}
# Clips or compositors that are copy/pasted with CTRL+C, CTRL+V
# Code using this assumes that this is saved as tuple (type_info, paste_data)
_copy_paste_objects = None
# Used to alter gui layout and tracks configuration, set at startup
SCREEN_HEIGHT = -1
SCREEN_WIDTH = -1
# Runtime environment data
gtk_version = None
mlt_version = None
appversion = "2.4.0"
RUNNING_FROM_INSTALLATION = 0
RUNNING_FROM_DEV_VERSION = 1
RUNNING_FROM_FLATPAK = 2
app_running_from = RUNNING_FROM_INSTALLATION
audio_monitoring_available = False
# Whether to let the user set their user_dir using XDG Base dir spec
use_xdg = False
# Cursor position and sensitivity
cursor_on_tline = False
cursor_is_tline_sensitive = True
last_mouse_x = -1 # This is only used by multitrimmode.py
last_mouse_y = -1 # This is only used by multitrimmode.py
# Flag for running JACK audio server. If this is on when SDLConsumer created in mltplayer.py
# jack rack filter will bw attached to it
# NOT USED CURRENTLY.
attach_jackrack = False
# Flag is used to block unwanted draw events during loads
project_is_loading = False
# Audio levels display mode, False means that audio levels are displayed on request
display_all_audio_levels = True
display_clip_media_thumbnails = True
# Flag for window being in fullscreen mode
fullscreen = False
# Trim view mode
show_trim_view = appconsts.TRIM_VIEW_OFF
# Remember fade and transition lengths for next invocation, users prefer this over one default value.
fade_length = -1
transition_length = -1
# Trim clips cache for quicker inits, path -> clip
_trim_clips_cache = {}
def current_is_move_mode():
if ((edit_mode == INSERT_MOVE) or (edit_mode == OVERWRITE_MOVE) or (edit_mode == MULTI_MOVE)):
return True
return False
def current_is_active_trim_mode():
if ((edit_mode == ONE_ROLL_TRIM) or (edit_mode == TWO_ROLL_TRIM) or (edit_mode == SLIDE_TRIM)):
return True
return False
def current_sequence():
return project.c_seq
def current_bin():
return project.c_bin
def current_proxy_media_paths():
return _current_proxy_paths
def update_current_proxy_paths():
global _current_proxy_paths
_current_proxy_paths = project.get_current_proxy_paths()
def current_tline_frame():
if timeline_visible():
return PLAYER().current_frame()
else:
return tline_shadow_frame
def PROJECT():
return project
def PLAYER():
return player
def EDIT_MODE():
return edit_mode
def MONITOR_MEDIA_FILE():
return _monitor_media_file
def auto_follow_active():
if get_compositing_mode() == appconsts.COMPOSITING_MODE_TOP_DOWN_FREE_MOVE:
return False
else:
return True
def get_compositing_mode():
if project.c_seq == None:
print ("get_compositing_mode(), trying to get compositing mode when no current sequence available!")
return appconsts.COMPOSITING_MODE_TOP_DOWN_FREE_MOVE
else:
return project.c_seq.compositing_mode
def get_track(index):
return project.c_seq.tracks[index]
def timeline_visible():
return _timeline_displayed
def mlt_version_is_equal_or_greater(test_version):
runtime_ver = mlt_version.split(".")
test_ver = test_version.split(".")
if runtime_ver[0] >= test_ver[0]:
if runtime_ver[1] >= test_ver[1]:
if runtime_ver[2] >= test_ver[2]:
return True
return False
def mlt_version_is_greater_correct(test_version):
runtime_ver = mlt_version.split(".")
test_ver = test_version.split(".")
if runtime_ver[0] > test_ver[0]:
return True
elif runtime_ver[0] == test_ver[0]:
if runtime_ver[1] > test_ver[1]:
return True
elif runtime_ver[1] == test_ver[1]:
if runtime_ver[2] > test_ver[2]:
return True
return False
def runtime_version_greater_then_test_version(test_version, runtime_version):
runtime_ver = runtime_version.split(".")
test_ver = test_version.split(".")
if runtime_ver[0] > test_ver[0]:
return True
elif runtime_ver[0] == test_ver[0]:
if runtime_ver[1] > test_ver[1]:
return True
elif runtime_ver[1] == test_ver[1]:
if runtime_ver[2] > test_ver[2]:
return True
return False
def set_copy_paste_objects(objs):
global _copy_paste_objects
_copy_paste_objects = objs
def get_copy_paste_objects():
return _copy_paste_objects
def screen_size_small_height():
if SCREEN_HEIGHT < 901:
return True
else:
if SCREEN_WIDTH < 1280:
return True
return False
def screen_size_small_width():
if SCREEN_WIDTH < 1420:
return True
else:
return False
def screen_size_small():
if screen_size_small_height() == True or screen_size_small_width() == True:
return True
return False
def screen_size_large_height():
if SCREEN_HEIGHT > 1048:
return True
else:
return False
def get_cached_trim_clip(path):
try:
return _trim_clips_cache[path]
except:
return None
def add_cached_trim_clip(clip):
_trim_clips_cache[clip.path] = clip
def clear_trim_clip_cache():
global _trim_clips_cache
_trim_clips_cache = {}
# Called from tline "motion_notify_event" when drag is not on.
# This is only used by multitrimmode.py to have data to enter trims with keyboard correctly
def set_mouse_current_non_drag_pos(x, y):
global last_mouse_x, last_mouse_y
last_mouse_x = x
last_mouse_y = y
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/editorwindow.py 0000664 0000000 0000000 00000216444 13610327166 0026206 0 ustar 00root root 0000000 0000000 """
Flowblade Movie Editor is a nonlinear video editor.
Copyright 2012 Janne Liljeblad.
This file is part of Flowblade Movie Editor .
Flowblade Movie Editor 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.
Flowblade Movie Editor is distributed in the hope that 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 Flowblade Movie Editor. If not, see .
"""
"""
Module contains main editor window object.
"""
import cairo
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import Pango
import app
import appconsts
import audiomonitoring
import audiosync
import batchrendering
import boxmove
import clipeffectseditor
import clipmenuaction
import compositeeditor
import dialogs
import dialogutils
import diskcachemanagement
import dnd
import editevent
import editorpersistance
import editorstate
import exporting
import glassbuttons
import gmic
import gui
import guicomponents
import guiutils
import keyevents
import medialinker
import medialog
import menuactions
import middlebar
import modesetting
import monitorevent
import monitorwidget
import respaths
import render
import rendergui
import panels
import patternproducer
from positionbar import PositionBar
import preferenceswindow
import projectaction
import projectinfogui
import proxyediting
import titler
import tlineaction
import tlinewidgets
import trackaction
import updater
import undo
import workflow
# GUI size params
MEDIA_MANAGER_WIDTH = 110
MONITOR_AREA_WIDTH = 600 # defines app min width with NOTEBOOK_WIDTH 400 for small
IMG_PATH = None
DARK_BG_COLOR = (0.223, 0.247, 0.247, 1.0)
# Cursors
OVERWRITE_CURSOR = None
OVERWRITE_BOX_CURSOR = None
INSERTMOVE_CURSOR = None
ONEROLL_CURSOR = None
ONEROLL_NO_EDIT_CURSOR = None
TWOROLL_CURSOR = None
TWOROLL_NO_EDIT_CURSOR = None
SLIDE_CURSOR = None
SLIDE_NO_EDIT_CURSOR = None
MULTIMOVE_CURSOR = None
ONEROLL_RIPPLE_CURSOR = None
CUT_CURSOR = None
KF_TOOL_CURSOR = None
MULTI_TRIM_CURSOR = None
ONEROLL_TOOL = None
OVERWRITE_TOOL = None
def _b(button, icon, remove_relief=False):
button.set_image(icon)
button.set_property("can-focus", False)
if remove_relief:
button.set_relief(Gtk.ReliefStyle.NONE)
def _toggle_image_switch(widget, icons):
not_pressed, pressed = icons
if widget.get_active() == True:
widget.set_image(pressed)
else:
widget.set_image(not_pressed)
def top_level_project_panel():
if editorpersistance.prefs.top_row_layout == appconsts.ALWAYS_TWO_PANELS:
return False
if editorpersistance.prefs.top_level_project_panel == True and editorstate.SCREEN_WIDTH > 1440 and editorstate.SCREEN_HEIGHT > 898:
return True
return False
class EditorWindow:
def __init__(self):
global IMG_PATH
IMG_PATH = respaths.IMAGE_PATH
# Read cursors
global INSERTMOVE_CURSOR, OVERWRITE_CURSOR, TWOROLL_CURSOR, ONEROLL_CURSOR, \
ONEROLL_NO_EDIT_CURSOR, TWOROLL_NO_EDIT_CURSOR, SLIDE_CURSOR, SLIDE_NO_EDIT_CURSOR, \
MULTIMOVE_CURSOR, MULTIMOVE_NO_EDIT_CURSOR, ONEROLL_RIPPLE_CURSOR, ONEROLL_TOOL, \
OVERWRITE_BOX_CURSOR, OVERWRITE_TOOL, CUT_CURSOR, KF_TOOL_CURSOR, MULTI_TRIM_CURSOR
# Aug-2019 - SvdB - BB
INSERTMOVE_CURSOR = guiutils.get_cairo_image("insertmove_cursor")
OVERWRITE_CURSOR = guiutils.get_cairo_image("overwrite_cursor")
OVERWRITE_BOX_CURSOR = guiutils.get_cairo_image("overwrite_cursor_box")
TWOROLL_CURSOR = guiutils.get_cairo_image("tworoll_cursor")
SLIDE_CURSOR = guiutils.get_cairo_image("slide_cursor")
ONEROLL_CURSOR = guiutils.get_cairo_image("oneroll_cursor")
ONEROLL_NO_EDIT_CURSOR = guiutils.get_cairo_image("oneroll_noedit_cursor")
TWOROLL_NO_EDIT_CURSOR = guiutils.get_cairo_image("tworoll_noedit_cursor")
SLIDE_NO_EDIT_CURSOR = guiutils.get_cairo_image("slide_noedit_cursor")
MULTIMOVE_CURSOR = guiutils.get_cairo_image("multimove_cursor")
MULTIMOVE_NO_EDIT_CURSOR = guiutils.get_cairo_image("multimove_cursor")
ONEROLL_TOOL = guiutils.get_cairo_image("oneroll_tool")
ONEROLL_RIPPLE_CURSOR = guiutils.get_cairo_image("oneroll_cursor_ripple")
OVERWRITE_TOOL = guiutils.get_cairo_image("overwrite_tool")
CUT_CURSOR = guiutils.get_cairo_image("cut_cursor")
KF_TOOL_CURSOR = guiutils.get_cairo_image("kftool_cursor")
MULTI_TRIM_CURSOR = guiutils.get_cairo_image("multitrim_cursor")
# Context cursors
self.context_cursors = {appconsts.POINTER_CONTEXT_END_DRAG_LEFT:(cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "ctx_drag_left.png"), 3, 7),
appconsts.POINTER_CONTEXT_END_DRAG_RIGHT:(cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "ctx_drag_right.png"), 14, 7),
appconsts.POINTER_CONTEXT_TRIM_LEFT:(cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "ctx_trim_left.png"), 9, 9),
appconsts.POINTER_CONTEXT_TRIM_RIGHT:(cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "ctx_trim_right.png"), 9, 9),
appconsts.POINTER_CONTEXT_BOX_SIDEWAYS:(cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "ctx_sideways.png"), 9, 9),
appconsts.POINTER_CONTEXT_COMPOSITOR_MOVE:(cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "ctx_sideways.png"), 9, 9),
appconsts.POINTER_CONTEXT_COMPOSITOR_END_DRAG_LEFT:(cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "ctx_drag_left.png"), 9, 9),
appconsts.POINTER_CONTEXT_COMPOSITOR_END_DRAG_RIGHT:(cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "ctx_drag_right.png"), 9, 9),
appconsts.POINTER_CONTEXT_MULTI_ROLL:(cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "tworoll_cursor.png"), 11, 9),
appconsts.POINTER_CONTEXT_MULTI_SLIP:(cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "slide_cursor.png"), 9, 9)}
# Window
self.window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
self.window.set_icon_from_file(respaths.IMAGE_PATH + "flowbladeappicon.png")
self.window.set_border_width(5)
self.window2 = None
if editorpersistance.prefs.global_layout != appconsts.SINGLE_WINDOW:
self.window2 = Gtk.Window(Gtk.WindowType.TOPLEVEL)
self.window2.set_icon_from_file(respaths.IMAGE_PATH + "flowbladeappicon.png")
self.window2.set_border_width(5)
self.window2.connect("delete-event", lambda w, e:app.shutdown())
# To ask confirmation for shutdown
self.window.connect("delete-event", lambda w, e:app.shutdown())
# Player consumer has to be stopped and started when window resized
self.window.connect("window-state-event", lambda w, e:updater.refresh_player(e))
# Build menubar
# Menubar build resources
menu_actions = [
('FileMenu', None, _('_File')),
('New', None, _('_New...'), 'N', None, lambda a:projectaction.new_project()),
('Open', None, _('_Open...'), 'O', None, lambda a:projectaction.load_project()),
('OpenRecent', None, _('Open Recent')),
('Save', None, _('_Save'), 'S', None, lambda a:projectaction.save_project()),
('Save As', None, _('_Save As...'), None, None, lambda a:projectaction.save_project_as()),
('SaveSnapshot', None, _('Save Backup Snapshot...'), None, None, lambda a:projectaction.save_backup_snapshot()),
('ExportMenu', None, _('Export')),
('ExportMeltXML', None, _('MLT XML'), None, None, lambda a:exporting.MELT_XML_export()),
('ExportEDL', None, _('EDL'), None, None, lambda a:exporting.EDL_export()),
('ExportScreenshot', None, _('Current Frame'), None, None, lambda a:exporting.screenshot_export()),
('ExportToArdour', None, _('Current Sequence Audio As Ardour Session'), None, None, lambda a:exporting.ardour_export()),
('Close', None, _('_Close'), None, None, lambda a:projectaction.close_project()),
('Quit', None, _('_Quit'), 'Q', None, lambda a:app.shutdown()),
('EditMenu', None, _('_Edit')),
('Undo', None, _('_Undo'), 'Z', None, undo.do_undo_and_repaint),
('Redo', None, _('_Redo'), 'Y', None, undo.do_redo_and_repaint),
('Copy', None, _('Copy'), 'C', None, lambda a:keyevents.copy_action()),
('Paste', None, _('Paste'), 'V', None, lambda a:keyevents.paste_action()),
('PasteFilters', None, _('Paste Filters / Properties'), 'V', None, lambda a:tlineaction.do_timeline_filters_paste()),
('AddFromMonitor', None, _('Add Monitor Clip')),
('AppendClip', None, _('Append'), None, None, lambda a:tlineaction.append_button_pressed()),
('InsertClip', None, _('Insert'), None, None, lambda a:tlineaction.insert_button_pressed()),
('ThreepointOverWriteClip', None, _('Three Point Overwrite'), None, None, lambda a:tlineaction.three_point_overwrite_pressed()),
('RangeOverWriteClip', None, _('Range Overwrite'), None, None, lambda a:tlineaction.range_overwrite_pressed()),
('CutClip', None, _('Cut Clip'), None, None, lambda a:tlineaction.cut_pressed()),
('SequenceSplit', None, _('Split to new Sequence at Playhead Position'), None, None, lambda a:tlineaction.sequence_split_pressed()),
('DeleteClip', None, _('Lift'), None, None, lambda a:tlineaction.lift_button_pressed()),
('SpliceOutClip', None, _('Splice Out'), None, None, lambda a:tlineaction.splice_out_button_pressed()),
('ResyncSelected', None, _('Resync'), None, None, lambda a:tlineaction.resync_button_pressed()),
('SetSyncParent', None, _('Set Sync Parent'), None, None, lambda a:_this_is_not_used()),
('AddTransition', None, _('Add Single Track Transition'), None, None, lambda a:tlineaction.add_transition_menu_item_selected()),
('AddFade', None, _('Add Single Track Fade'), None, None, lambda a:tlineaction.add_fade_menu_item_selected()),
('ClearFilters', None, _('Clear Filters'), None, None, lambda a:clipmenuaction.clear_filters()),
('Timeline', None, _('Timeline')),
('FiltersOff', None, _('All Filters Off'), None, None, lambda a:tlineaction.all_filters_off()),
('FiltersOn', None, _('All Filters On'), None, None, lambda a:tlineaction.all_filters_on()),
('SyncCompositors', None, _('Sync All Compositors'), 'S', None, lambda a:tlineaction.sync_all_compositors()),
('CompositorsFadesDefaults', None, _('Set Compositor Auto Fades...'), None, None, lambda a:tlineaction.set_compositors_fades_defaults()),
('ChangeSequenceTracks', None, _('Change Sequence Tracks Count...'), None, None, lambda a:projectaction.change_sequence_track_count()),
('Watermark', None, _('Watermark...'), None, None, lambda a:menuactions.edit_watermark()),
('DiskCacheManager', None, _('Disk Cache Manager'), None, None, lambda a:diskcachemanagement.show_disk_management_dialog()),
('ProfilesManager', None, _('Profiles Manager'), None, None, lambda a:menuactions.profiles_manager()),
('Preferences', None, _('Preferences'), None, None, lambda a:preferenceswindow.preferences_dialog()),
('ViewMenu', None, _('View')),
('FullScreen', None, _('Fullscreen'), 'F11', None, lambda a:menuactions.toggle_fullscreen()),
('ProjectMenu', None, _('Project')),
('AddMediaClip', None, _('Add Video, Audio or Image...'), None, None, lambda a: projectaction.add_media_files()),
('AddImageSequence', None, _('Add Image Sequence...'), None, None, lambda a:projectaction.add_image_sequence()),
('CreateColorClip', None, _('Create Color Clip...'), None, None, lambda a:patternproducer.create_color_clip()),
('BinMenu', None, _('Bin')),
('AddBin', None, _('Add Bin'), None, None, lambda a:projectaction.add_new_bin()),
('DeleteBin', None, _('Delete Selected Bin'), None, None, lambda a:projectaction.delete_selected_bin()),
('SequenceMenu', None, _('Sequence')),
('AddSequence', None, _('Add New Sequence'), None, None, lambda a:projectaction.add_new_sequence()),
('EditSequence', None, _('Edit Selected Sequence'), None, None, lambda a:projectaction.change_edit_sequence()),
('DeleteSequence', None, _('Delete Selected Sequence'), None, None, lambda a:projectaction.delete_selected_sequence()),
('CompositingModeMenu', None, _('Compositing Mode')),
('PatternProducersMenu', None, _('Create Pattern Producer')),
('CreateNoiseClip', None, _('Noise'), None, None, lambda a:patternproducer.create_noise_clip()),
('CreateBarsClip', None, _('EBU Bars'), None, None, lambda a:patternproducer.create_bars_clip()),
('CreateIsingClip', None, _('Ising'), None, None, lambda a:patternproducer.create_icing_clip()),
('CreateColorPulseClip', None, _('Color Pulse'), None, None, lambda a:patternproducer.create_color_pulse_clip()),
('CreateCountClip', None, _('Count'), None, None, lambda a:patternproducer.create_count_clip()),
('CompoundClipsMenu', None, _('Create Compound Clip')),
('CreateSelectionCompound', None, _('From Selected Clips'), None, None, lambda a:projectaction.create_selection_compound_clip()),
('CreateSequenceCompound', None, _('From Current Sequence'), None, None, lambda a:projectaction.create_sequence_compound_clip()),
('CreateSequenceFreezeCompound', None, _('From Current Sequence With Freeze Frame at Playhead Position'), None, None, lambda a:projectaction.create_sequence_freeze_frame_compound_clip()),
('AudioSyncCompoundClip', None, _('Audio Sync Merge Clip From 2 Media Items '), None, None, lambda a:audiosync.create_audio_sync_compound_clip()),
('ImportProjectMedia', None, _('Import Media From Project...'), None, None, lambda a:projectaction.import_project_media()),
('CombineSequences', None, _('Import Another Sequence Into This Sequence...'), None, None, lambda a:projectaction.combine_sequences()),
('LogClipRange', None, _('Log Marked Clip Range'), 'L', None, lambda a:medialog.log_range_clicked()),
('ViewProjectEvents', None, _('View Project Events...'), None, None, lambda a:projectaction.view_project_events()),
('RecreateMediaIcons', None, _('Recreate Media Icons...'), None, None, lambda a:menuactions.recreate_media_file_icons()),
('RemoveUnusedMedia', None, _('Remove Unused Media...'), None, None, lambda a:projectaction.remove_unused_media()),
('ChangeProfile', None, _("Change Project Profile..."), None, None, lambda a: projectaction.change_project_profile()),
('ProxyManager', None, _('Proxy Manager'), None, None, lambda a:proxyediting.show_proxy_manager_dialog()),
('ProjectInfo', None, _('Project Info'), None, None, lambda a:menuactions.show_project_info()),
('RenderMenu', None, _('Render')),
('AddToQueue', None, _('Add To Batch Render Queue...'), None, None, lambda a:projectaction.add_to_render_queue()),
('BatchRender', None, _('Batch Render Queue'), None, None, lambda a:batchrendering.launch_batch_rendering()),
('ReRenderTransitionsFades', None, _('Rerender All Rendered Transitions And Fades '), None, None, lambda a:tlineaction.rerender_all_rendered_transitions_and_fades()),
('Render', None, _('Render Timeline'), None, None, lambda a:projectaction.do_rendering()),
('ToolsMenu', None, _('Tools')),
('Titler', None, _('Titler'), None, None, lambda a:titler.show_titler()),
('AudioMix', None, _('Audio Mixer'), None, None, lambda a:audiomonitoring.show_audio_monitor()),
('GMIC', None, _("G'MIC Effects"), None, None, lambda a:gmic.launch_gmic()),
('MediaLink', None, _('Media Relinker'), None, None, lambda a:medialinker.display_linker()),
('HelpMenu', None, _('_Help')),
('QuickReference', None, _('Contents'), None, None, lambda a:menuactions.quick_reference()),
('Environment', None, _('Runtime Environment'), None, None, lambda a:menuactions.environment()),
('KeyboardShortcuts', None, _('Keyboard Shortcuts'), None, None, lambda a:dialogs.keyboard_shortcuts_dialog(self.window, workflow.get_tline_tool_working_set, menuactions.keyboard_shortcuts_callback)),
('About', None, _('About'), None, None, lambda a:menuactions.about()),
('TOOL_ACTION_KEY_1', None, None, '1', None, lambda a:_this_is_not_used()),
('TOOL_ACTION_KEY_2', None, None, '2', None, lambda a:_this_is_not_used()),
('TOOL_ACTION_KEY_3', None, None, '3', None, lambda a:_this_is_not_used()),
('TOOL_ACTION_KEY_4', None, None, '4', None, lambda a:_this_is_not_used()),
('TOOL_ACTION_KEY_5', None, None, '5', None, lambda a:_this_is_not_used()),
('TOOL_ACTION_KEY_6', None, None, '6', None, lambda a:_this_is_not_used()),
('TOOL_ACTION_KEY_7', None, None, '7', None, lambda a:_this_is_not_used()),
('TOOL_ACTION_KEY_8', None, None, '8', None, lambda a:_this_is_not_used()),
('TOOL_ACTION_KEY_9', None, None, '9', None, lambda a:_this_is_not_used())
]
menu_string = """
"""
self.fblade_theme_fix_panels = []
self.fblade_theme_fix_panels_darker = []
# Create global action group
action_group = Gtk.ActionGroup('WindowActions')
action_group.add_actions(menu_actions, user_data=None)
# Create UIManager and add accelators to window
ui = Gtk.UIManager()
ui.insert_action_group(action_group, 0)
ui.add_ui_from_string(menu_string)
accel_group = ui.get_accel_group()
self.window.add_accel_group(accel_group)
# Get menu bar
self.menubar = ui.get_widget('/MenuBar')
# Set reference to UI manager and acclegroup
self.uimanager = ui
self.accel_group = accel_group
# Add recent projects to menu
editorpersistance.fill_recents_menu_widget(ui.get_widget('/MenuBar/FileMenu/OpenRecent'), projectaction.open_recent_project)
# Disable audio mixer if not available
if editorstate.audio_monitoring_available == False:
ui.get_widget('/MenuBar/ToolsMenu/AudioMix').set_sensitive(False)
# Media panel
self.bin_list_view = guicomponents.BinTreeView(
projectaction.bin_selection_changed,
projectaction.bin_name_edited,
projectaction.bins_panel_popup_requested)
dnd.connect_bin_tree_view(self.bin_list_view.treeview, projectaction.move_files_to_bin)
self.bin_list_view.set_property("can-focus", True)
self.bins_panel = panels.get_bins_tree_panel(self.bin_list_view)
self.bins_panel.set_size_request(MEDIA_MANAGER_WIDTH, 10) # this component is always expanded, so 10 for minimum size ok
self.media_list_view = guicomponents.MediaPanel(projectaction.media_file_menu_item_selected,
projectaction.media_panel_double_click,
projectaction.media_panel_popup_requested)
view = Gtk.Viewport()
view.add(self.media_list_view.widget)
view.set_shadow_type(Gtk.ShadowType.NONE)
self.media_scroll_window = Gtk.ScrolledWindow()
self.media_scroll_window.add(view)
self.media_scroll_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
self.media_scroll_window.set_size_request(guicomponents.MEDIA_OBJECT_WIDGET_WIDTH * 2 + 70, guicomponents.MEDIA_OBJECT_WIDGET_HEIGHT)
self.media_scroll_window.show_all()
media_panel, bin_info = panels.get_media_files_panel(
self.media_scroll_window,
lambda w,e: projectaction.add_media_files(),
lambda w,e: projectaction.delete_media_files(),
projectaction.columns_count_launch_pressed,
projectaction.hamburger_pressed, # lambda w,e: proxyediting.create_proxy_files_pressed(),
projectaction.media_filtering_select_pressed)
guiutils.set_margins(media_panel, 6, 6, 4, 6)
self.media_panel = media_panel
self.bin_info = bin_info
# Smallest screens always get bins in same panel as media, others get top level project panel if selected
if top_level_project_panel() == True:
self.mm_paned = Gtk.HBox()
self.mm_paned.add(media_panel)
else:
self.mm_paned = Gtk.HPaned()
guiutils.set_margins(self.bins_panel, 6, 6, 8, 0)
self.mm_paned.pack1(self.bins_panel, resize=True, shrink=True)
self.mm_paned.pack2(media_panel, resize=True, shrink=False)
mm_panel = guiutils.set_margins(self.mm_paned, 0, 0, 0, 0)
# Effects panel
self.effect_select_list_view = guicomponents.FilterListView()
self.effect_select_combo_box = Gtk.ComboBoxText()
self.effect_select_list_view.treeview.connect("row-activated", clipeffectseditor.effect_select_row_double_clicked)
dnd.connect_effects_select_tree_view(self.effect_select_list_view.treeview)
clip_editor_panel, info_row = clipeffectseditor.get_clip_effects_editor_panel(
self.effect_select_combo_box,
self.effect_select_list_view)
clipeffectseditor.widgets.effect_stack_view.treeview.connect("button-press-event",
clipeffectseditor.filter_stack_button_press)
if not(editorstate.SCREEN_HEIGHT < 1023):
effects_editor_panel = guiutils.set_margins(clipeffectseditor.widgets.value_edit_frame, 0, 0, 8, 0)
else:
guiutils.set_margins(clip_editor_panel, 4, 4, 4, 0)
effects_editor_panel = guiutils.set_margins(clipeffectseditor.widgets.value_edit_frame, 4, 0, 4, 4)
effects_hbox = Gtk.HBox()
effects_hbox.set_border_width(0)
effects_hbox.pack_start(clip_editor_panel, False, False, 0)
effects_hbox.pack_start(effects_editor_panel, True, True, 0)
effects_vbox = Gtk.VBox()
effects_vbox.pack_start(effects_hbox, True, True, 0)
effects_vbox.pack_start(info_row, False, False, 0)
if not(editorstate.SCREEN_HEIGHT < 1023):
self.effects_panel = guiutils.set_margins(effects_vbox, 8, 0, 7, 2)
else:
self.effects_panel = effects_vbox
self.fblade_theme_fix_panels.append(self.effects_panel) # may not be needed?
# Compositors panel
action_row = compositeeditor.get_compositor_clip_panel()
compositor_editor_panel = guiutils.set_margins(compositeeditor.widgets.value_edit_frame, 0, 0, 4, 0)
compositors_hbox = Gtk.HBox()
compositors_hbox.pack_start(compositor_editor_panel, True, True, 0)
compositors_vbox = Gtk.VBox()
compositors_vbox.pack_start(compositors_hbox, True, True, 0)
compositors_vbox.pack_start(action_row, False, False, 0)
self.compositors_panel = guiutils.set_margins(compositors_vbox, 2, 2, 2, 2)
self.fblade_theme_fix_panels.append(self.compositors_panel)
# Render panel
try:
render.create_widgets()
render_panel_left = rendergui.get_render_panel_left(render.widgets)
except IndexError:
print("No rendering options found")
render_panel_left = None
# 'None' here means that no possible rendering options were available
# and creating panel failed. Inform user of this and hide render GUI
if render_panel_left == None:
render_hbox = Gtk.VBox(False, 5)
render_hbox.pack_start(Gtk.Label(label="Rendering disabled."), False, False, 0)
render_hbox.pack_start(Gtk.Label(label="No available rendering options found."), False, False, 0)
render_hbox.pack_start(Gtk.Label(label="See Help->Environment->Render Options for details."), False, False, 0)
render_hbox.pack_start(Gtk.Label(label="Install codecs to make rendering available."), False, False, 0)
render_hbox.pack_start(Gtk.Label(label=" "), True, True, 0)
else: # all is good
render_panel_right = rendergui.get_render_panel_right(render.widgets,
lambda w,e: projectaction.do_rendering(),
lambda w,e: projectaction.add_to_render_queue())
if editorstate.screen_size_small_width() == False:
render_hbox = Gtk.HBox(True, 5)
else:
render_hbox = Gtk.HBox(False, 5)
render_hbox.pack_start(render_panel_left, True, True, 0)
render_hbox.pack_start(render_panel_right, True, True, 0)
render_panel = guiutils.set_margins(render_hbox, 2, 6, 8, 6)
self.fblade_theme_fix_panels.append(render_panel)
# Range Log panel
media_log_events_list_view = medialog.get_media_log_list_view()
events_panel = medialog.get_media_log_events_panel(media_log_events_list_view)
media_log_vbox = Gtk.HBox()
media_log_vbox.pack_start(events_panel, True, True, 0)
self.fblade_theme_fix_panels.append(media_log_vbox)
media_log_panel = guiutils.set_margins(media_log_vbox, 6, 6, 6, 6)
self.media_log_events_list_view = media_log_events_list_view
self.fblade_theme_fix_panels.append(media_log_panel)
# Project Panel
# Sequence list
self.sequence_list_view = guicomponents.SequenceListView( projectaction.sequence_name_edited,
projectaction.sequence_panel_popup_requested,
projectaction.sequence_list_double_click_done)
seq_panel = panels.get_sequences_panel(
self.sequence_list_view,
lambda w,e: projectaction.change_edit_sequence(),
lambda w,e: projectaction.add_new_sequence(),
lambda w,e: projectaction.delete_selected_sequence())
if top_level_project_panel() == True:
# Project info
project_info_panel = projectinfogui.get_top_level_project_info_panel()
PANEL_WIDTH = 10
PANEL_HEIGHT = 150
top_project_vbox = Gtk.VBox()
top_project_vbox.pack_start(project_info_panel, False, False, 0)
top_project_vbox.pack_start(self.bins_panel, True, True, 0)
top_project_vbox.pack_start(seq_panel, True, True, 0)
top_project_vbox.set_size_request(PANEL_WIDTH, PANEL_HEIGHT)
top_project_panel = guiutils.set_margins(top_project_vbox, 0, 2, 6, 2)
else:
# Notebook project panel for smallest screens
# Project info
project_info_panel = projectinfogui.get_project_info_panel()
# Project vbox and panel
project_vbox = Gtk.VBox()
project_vbox.pack_start(project_info_panel, False, True, 0)
project_vbox.pack_start(seq_panel, True, True, 0)
project_panel = guiutils.set_margins(project_vbox, 0, 2, 6, 2)
# Notebook
self.notebook = Gtk.Notebook()
self.notebook.set_size_request(appconsts.NOTEBOOK_WIDTH, appconsts.TOP_ROW_HEIGHT)
media_label = Gtk.Label(label=_("Media"))
media_label.no_dark_bg = True
if editorpersistance.prefs.global_layout == appconsts.SINGLE_WINDOW:
self.notebook.append_page(mm_panel, media_label)
self.notebook.append_page(media_log_panel, Gtk.Label(label=_("Range Log")))
self.notebook.append_page(self.effects_panel, Gtk.Label(label=_("Filters")))
self.notebook.append_page(self.compositors_panel, Gtk.Label(label=_("Compositors")))
if top_level_project_panel() == False:
self.notebook.append_page(project_panel, Gtk.Label(label=_("Project")))
self.notebook.append_page(render_panel, Gtk.Label(label=_("Render")))
self.notebook.set_tab_pos(Gtk.PositionType.BOTTOM)
# Position bar and decorative frame for it
self.pos_bar = PositionBar()
pos_bar_frame = Gtk.Frame()
pos_bar_frame.add(self.pos_bar.widget)
pos_bar_frame.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
pos_bar_frame.set_margin_top(4)
pos_bar_frame.set_margin_bottom(4)
pos_bar_frame.set_margin_left(6)
# Play buttons row
self._create_monitor_buttons()
self._create_monitor_row_widgets()
self.player_buttons = glassbuttons.PlayerButtons()
tooltips = [_("Prev Frame - Arrow Left"), _("Next Frame - Arrow Right"), _("Play - Space"), _("Stop - Space"), _("Mark In - I"), _("Mark Out - O"), _("Clear Marks"), _("To Mark In"), _("To Mark Out")]
tooltip_runner = glassbuttons.TooltipRunner(self.player_buttons, tooltips)
if editorpersistance.prefs.buttons_style == 2: # NO_DECORATIONS
self.player_buttons.no_decorations = True
self.view_mode_select = guicomponents.get_monitor_view_select_combo(lambda w, e: tlineaction.view_mode_menu_lauched(w, e))
self.trim_view_select = guicomponents.get_trim_view_select_combo(lambda w, e: monitorevent.trim_view_menu_launched(w, e))
player_buttons_row = Gtk.HBox(False, 0)
player_buttons_row.pack_start(self.monitor_switch.widget, False, False, 0)
player_buttons_row.pack_start(Gtk.Label(), True, True, 0)
player_buttons_row.pack_start(self.player_buttons.widget, False, False, 0)
player_buttons_row.pack_start(Gtk.Label(), True, True, 0)
player_buttons_row.pack_start(self.trim_view_select.widget, False, False, 0)
player_buttons_row.pack_start(self.view_mode_select.widget, False, False, 0)
player_buttons_row.set_margin_bottom(2)
self.fblade_theme_fix_panels_darker.append(player_buttons_row)
# Switch / pos bar row
sw_pos_hbox = Gtk.HBox(False, 1)
sw_pos_hbox.pack_start(pos_bar_frame, True, True, 0)
sw_pos_hbox.set_margin_top(4)
sw_pos_hbox.set_margin_left(2)
# Video display
monitor_widget = monitorwidget.MonitorWidget()
self.tline_display = monitor_widget.get_monitor()
self.monitor_widget = monitor_widget
dnd.connect_video_monitor(self.tline_display)
# Monitor
monitor_vbox = Gtk.VBox(False, 1)
monitor_vbox.pack_start(monitor_widget.widget, True, True, 0)
monitor_vbox.pack_start(sw_pos_hbox, False, True, 0)
monitor_vbox.pack_start(player_buttons_row, False, True, 0)
monitor_align = guiutils.set_margins(monitor_vbox, 3, 0, 3, 3)
monitor_frame = Gtk.Frame()
monitor_frame.add(monitor_align)
monitor_frame.set_shadow_type(Gtk.ShadowType.ETCHED_OUT)
monitor_frame.set_size_request(MONITOR_AREA_WIDTH, appconsts.TOP_ROW_HEIGHT)
# Notebook panel
notebook_vbox = Gtk.VBox(False, 1)
notebook_vbox.no_dark_bg = True
notebook_vbox.pack_start(self.notebook, True, True, 0)
# Top row paned
self.top_paned = Gtk.HPaned()
if editorpersistance.prefs.global_layout == appconsts.SINGLE_WINDOW:
self.top_paned.pack1(notebook_vbox, resize=False, shrink=False)
self.top_paned.pack2(monitor_frame, resize=True, shrink=False)
else:
self.top_paned.pack1(mm_panel, resize=False, shrink=False)
self.top_paned.pack2(notebook_vbox, resize=True, shrink=False)
# Top row
self.top_row_hbox = Gtk.HBox(False, 0)
if top_level_project_panel() == True:
self.top_row_hbox.pack_start(top_project_panel, False, False, 0)
self.top_row_hbox.pack_start(self.top_paned, True, True, 0)
self._update_top_row()
# Edit buttons rows
self.edit_buttons_row = self._get_edit_buttons_row()
if editorpersistance.prefs.theme == appconsts.LIGHT_THEME:
self.edit_buttons_frame = Gtk.Frame()
self.edit_buttons_frame.add(self.edit_buttons_row)
self.edit_buttons_frame.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
else:
self.edit_buttons_frame = self.edit_buttons_row
# Timeline scale
self.tline_scale = tlinewidgets.TimeLineFrameScale(modesetting.set_default_edit_mode,
updater.mouse_scroll_zoom)
self.tline_info = Gtk.HBox()
info_contents = Gtk.Label()
self.tline_info.add(info_contents)
self.tline_info.info_contents = info_contents # this switched and saved as member of its container
info_h = Gtk.HBox()
info_h.pack_start(self.tline_info, False, False, 0)
info_h.pack_start(Gtk.Label(), True, True, 0)
# Aug-2019 - SvdB - BB - Height doesn't need to be doubled. 1.4x is nicer
size_adj = 1
size_x = tlinewidgets.COLUMN_WIDTH - 22 - 22 - 22
size_y = tlinewidgets.SCALE_HEIGHT
if editorpersistance.prefs.double_track_hights:
size_adj = 1.4
size_x = tlinewidgets.COLUMN_WIDTH - (66*size_adj)
info_h.set_size_request(size_x, size_y)
# Aug-2019 - SvdB - BB - add size_adj and width/height as parameter to be able to adjust it for double height
marker_surface = guiutils.get_cairo_image("marker")
markers_launcher = guicomponents.get_markers_menu_launcher(tlineaction.marker_menu_lauch_pressed, marker_surface, 22*size_adj, 22*size_adj)
tracks_launcher_surface = guiutils.get_cairo_image("track_menu_launch")
tracks_launcher = guicomponents.PressLaunch(trackaction.all_tracks_menu_launch_pressed, tracks_launcher_surface, 22*size_adj, 22*size_adj)
levels_launcher_surface = guiutils.get_cairo_image("audio_levels_menu_launch")
levels_launcher = guicomponents.PressLaunch(trackaction.audio_levels_menu_launch_pressed, levels_launcher_surface, 22*size_adj, 22*size_adj)
levels_launcher_surface = guiutils.get_cairo_image("audio_levels_menu_launch")
levels_launcher = guicomponents.PressLaunch(trackaction.audio_levels_menu_launch_pressed, levels_launcher_surface, 22*size_adj, 22*size_adj)
# Comp mode selector test, comimg in 2.6
# comp_mode_surface = guiutils.get_cairo_image("standard_auto")
#comp_mode_launcher = guicomponents.PressLaunch(trackaction.audio_levels_menu_launch_pressed, comp_mode_surface, 22*size_adj, 22*size_adj)
# Timeline top row
tline_hbox_1 = Gtk.HBox()
#tline_hbox_1.pack_start(comp_mode_launcher.widget, False, False, 0)
tline_hbox_1.pack_start(info_h, False, False, 0)
tline_hbox_1.pack_start(levels_launcher.widget, False, False, 0)
tline_hbox_1.pack_start(tracks_launcher.widget, False, False, 0)
tline_hbox_1.pack_start(markers_launcher.widget, False, False, 0)
tline_hbox_1.pack_start(self.tline_scale.widget, True, True, 0)
# Timeline column
self.tline_column = tlinewidgets.TimeLineColumn(
trackaction.track_active_switch_pressed,
trackaction.track_center_pressed,
trackaction.track_double_click)
# Timeline editpanel
self.tline_canvas = tlinewidgets.TimeLineCanvas(
editevent.tline_canvas_mouse_pressed,
editevent.tline_canvas_mouse_moved,
editevent.tline_canvas_mouse_released,
editevent.tline_canvas_double_click,
updater.mouse_scroll_zoom,
self.tline_cursor_leave,
self.tline_cursor_enter)
dnd.connect_tline(self.tline_canvas.widget, editevent.tline_effect_drop,
editevent.tline_media_drop)
# Timeline middle row
tline_hbox_2 = Gtk.HBox()
tline_hbox_2.pack_start(self.tline_column.widget, False, False, 0)
tline_hbox_2.pack_start(self.tline_canvas.widget, True, True, 0)
# Bottom row filler
self.left_corner = guicomponents.TimeLineLeftBottom()
self.left_corner.widget.set_size_request(tlinewidgets.COLUMN_WIDTH, 20)
# Timeline scroller
self.tline_scroller = tlinewidgets.TimeLineScroller(updater.tline_scrolled)
# Timeline bottom row
tline_hbox_3 = Gtk.HBox()
tline_hbox_3.pack_start(self.left_corner.widget, False, False, 0)
tline_hbox_3.pack_start(self.tline_scroller, True, True, 0)
# Timeline hbox
tline_vbox = Gtk.VBox()
tline_vbox.pack_start(tline_hbox_1, False, False, 0)
tline_vbox.pack_start(tline_hbox_2, True, True, 0)
tline_vbox.pack_start(tline_hbox_3, False, False, 0)
# Timeline box
self.tline_box = Gtk.HBox()
self.tline_box.pack_start(tline_vbox, True, True, 0)
# Timeline pane
tline_pane = Gtk.VBox(False, 1)
tline_pane.pack_start(self.edit_buttons_frame, False, True, 0)
tline_pane.pack_start(self.tline_box, True, True, 0)
self.tline_pane = tline_pane
# VPaned top row / timeline
self.app_v_paned = Gtk.VPaned()
self.app_v_paned.pack1(self.top_row_hbox, resize=False, shrink=False)
self.app_v_paned.pack2(tline_pane, resize=True, shrink=False)
self.app_v_paned.no_dark_bg = True
# Menu box
# menubar size 348, 28 if w want to center someting her with set_size_request
self.menubar.set_margin_bottom(4)
menu_vbox = Gtk.HBox(False, 0)
menu_vbox.pack_start(guiutils.get_right_justified_box([self.menubar]), False, False, 0)
menu_vbox.pack_start(Gtk.Label(), True, True, 0)
if editorpersistance.prefs.global_layout == appconsts.SINGLE_WINDOW:
menu_vbox.pack_start(self.monitor_tc_info.widget, False, False, 0)
else:
top_row_window_2 = Gtk.HBox(False, 0)
top_row_window_2.pack_start(Gtk.Label(), True, True, 0)
top_row_window_2.pack_start(self.monitor_tc_info.widget, False, False, 0)
# Pane
pane = Gtk.VBox(False, 1)
pane.pack_start(menu_vbox, False, True, 0)
pane.pack_start(self.app_v_paned, True, True, 0)
self.fblade_theme_fix_panels_darker.append(pane)
# Tooltips
self._add_tool_tips()
# GUI preferences
self._init_gui_to_prefs()
# Viewmenu initial state
self._init_view_menu(ui.get_widget('/MenuBar/ViewMenu'))
# Set pane and show window
self.window.add(pane)
self.window.set_title("Flowblade")
# Maximize if it seems that we exited maximized, else set size
w, h = editorpersistance.prefs.exit_allocation
if w != 0: # non-existing prefs file causes w and h to be 0
if (float(w) / editorstate.SCREEN_WIDTH > 0.95) and (float(h) / editorstate.SCREEN_HEIGHT > 0.95):
self.window.maximize()
else:
self.window.resize(w, h)
self.window.set_position(Gtk.WindowPosition.CENTER)
else:
self.window.set_position(Gtk.WindowPosition.CENTER)
# Show window and all of its components
self.window.show_all()
# Show Monitor Window in two window mode
if editorpersistance.prefs.global_layout != appconsts.SINGLE_WINDOW:
pane2 = Gtk.VBox(False, 1)
pane2.pack_start(top_row_window_2, False, False, 0)
pane2.pack_start(monitor_frame, True, True, 0)
# Set pane and show window
self.window2.add(pane2)
self.window2.set_title("Flowblade")
# Maximize if it seems that we exited maximized, else set size
w, h, x, y = editorpersistance.prefs.exit_allocation_window_2
if w != 0: # non-existing prefs file causes w and h to be 0
if (float(w) / editorstate.SCREEN_WIDTH > 0.95) and (float(h) / editorstate.SCREEN_HEIGHT > 0.95):
self.window2.maximize()
else:
self.window2.resize(w, h)
self.window2.move(x, y)
self.window2.show_all()
# Set paned positions
bin_w = editorpersistance.prefs.mm_paned_position
if bin_w < MEDIA_MANAGER_WIDTH + 2:
bin_w = 0
if top_level_project_panel() == False:
self.mm_paned.set_position(bin_w)
self.top_paned.set_position(editorpersistance.prefs.top_paned_position)
self.app_v_paned.set_position(editorpersistance.prefs.app_v_paned_position)
def _init_view_menu(self, menu_item):
menu = menu_item.get_submenu()
# Full Screen -tem is already in menu, we need separator here
sep = Gtk.SeparatorMenuItem()
menu.append(sep)
windows_menu_item = Gtk.MenuItem(_("Window Mode"))
windows_menu = Gtk.Menu()
one_window = Gtk.RadioMenuItem()
one_window.set_label(_("Single Window"))
windows_menu.append(one_window)
two_windows = Gtk.RadioMenuItem.new_with_label([one_window], _("Two Windows"))
if editorpersistance.prefs.global_layout == appconsts.SINGLE_WINDOW:
one_window.set_active(True)
else:
two_windows.set_active(True)
one_window.connect("activate", lambda w: self._change_windows_preference(w, appconsts.SINGLE_WINDOW))
two_windows.connect("activate", lambda w: self._change_windows_preference(w, appconsts.TWO_WINDOWS))
windows_menu.append(two_windows)
windows_menu_item.set_submenu(windows_menu)
menu.append(windows_menu_item)
mb_menu_item = Gtk.MenuItem(_("Middlebar Layout"))
mb_menu = Gtk.Menu()
tc_left = Gtk.RadioMenuItem()
tc_left.set_label(_("Timecode Left"))
tc_left.connect("activate", lambda w: middlebar._show_buttons_TC_LEFT_layout(w))
mb_menu.append(tc_left)
tc_middle = Gtk.RadioMenuItem.new_with_label([tc_left], _("Timecode Center"))
tc_middle.connect("activate", lambda w: middlebar._show_buttons_TC_MIDDLE_layout(w))
mb_menu.append(tc_middle)
components_centered = Gtk.RadioMenuItem.new_with_label([tc_left], _("Components Centered"))
components_centered.connect("activate", lambda w: middlebar._show_buttons_COMPONENTS_CENTERED_layout(w))
mb_menu.append(components_centered)
if editorpersistance.prefs.midbar_layout == appconsts.MIDBAR_COMPONENTS_CENTERED:
components_centered.set_active(True)
elif editorpersistance.prefs.midbar_layout == appconsts.MIDBAR_TC_LEFT:
tc_left.set_active(True)
else:
tc_middle.set_active(True)
mb_menu_item.set_submenu(mb_menu)
menu.append(mb_menu_item)
tabs_menu_item = Gtk.MenuItem(_("Tabs Position"))
tabs_menu = Gtk.Menu()
tabs_up = Gtk.RadioMenuItem()
tabs_up.set_label( _("Up"))
tabs_up.connect("activate", lambda w: self._show_tabs_up(w))
tabs_menu.append(tabs_up)
tabs_down = Gtk.RadioMenuItem.new_with_label([tabs_up], _("Down"))
tabs_down.connect("activate", lambda w: self._show_tabs_down(w))
if editorpersistance.prefs.tabs_on_top == True:
tabs_up.set_active(True)
else:
tabs_down.set_active(True)
tabs_menu.append(tabs_down)
tabs_menu_item.set_submenu(tabs_menu)
menu.append(tabs_menu_item)
sep = Gtk.SeparatorMenuItem()
menu.append(sep)
sep = Gtk.SeparatorMenuItem()
menu.append(sep)
interp_menu_item = Gtk.MenuItem(_("Monitor Playback Interpolation"))
interp_menu = Gtk.Menu()
interp_nearest = Gtk.RadioMenuItem()
interp_nearest.set_label(_("Nearest Neighbour (fast)"))
interp_nearest.connect("activate", lambda w: monitorevent.set_monitor_playback_interpolation("nearest"))
interp_menu.append(interp_nearest)
interp_bilinear = Gtk.RadioMenuItem.new_with_label([interp_nearest], _("Bilinear (good)"))
interp_bilinear.connect("activate", lambda w: monitorevent.set_monitor_playback_interpolation("bilinear"))
interp_menu.append(interp_bilinear)
interp_bicubic = Gtk.RadioMenuItem.new_with_label([interp_nearest], _("Bicubic (better)"))
interp_bicubic.set_active(True)
interp_bicubic.connect("activate", lambda w: monitorevent.set_monitor_playback_interpolation("bicubic"))
interp_menu.append(interp_bicubic)
interp_hyper = Gtk.RadioMenuItem.new_with_label([interp_nearest], _("Hyper/Lanczos (best)"))
interp_hyper.connect("activate", lambda w: monitorevent.set_monitor_playback_interpolation("hyper"))
interp_menu.append(interp_hyper)
interp_menu_item.set_submenu(interp_menu)
menu.append(interp_menu_item)
sep = Gtk.SeparatorMenuItem()
menu.append(sep)
zoom_in_menu_item = Gtk.MenuItem(_("Zoom In"))
zoom_in_menu_item.connect("activate", lambda w: updater.zoom_in())
menu.append(zoom_in_menu_item)
zoom_out_menu_item = Gtk.MenuItem(_("Zoom Out"))
zoom_out_menu_item.connect("activate", lambda w: updater.zoom_out())
menu.append(zoom_out_menu_item)
zoom_fit_menu_item = Gtk.MenuItem(_("Zoom Fit"))
zoom_fit_menu_item.connect("activate", lambda w: updater.zoom_project_length())
menu.append(zoom_fit_menu_item)
def init_compositing_mode_menu(self):
menu_item = self.uimanager.get_widget('/MenuBar/SequenceMenu/CompositingModeMenu')
menu = menu_item.get_submenu()
guiutils.remove_children(menu)
comp_top_free = Gtk.RadioMenuItem()
comp_top_free.set_label(_("Top Down Free Move"))
comp_top_free.show()
menu.append(comp_top_free)
comp_top_auto = Gtk.RadioMenuItem.new_with_label([comp_top_free],_("Top Down Auto Follow"))
comp_top_auto.show()
menu.append(comp_top_auto)
comp_standard_auto = Gtk.RadioMenuItem.new_with_label([comp_top_free],_("Standard Auto Follow"))
comp_standard_auto.show()
menu.append(comp_standard_auto)
menu_items = [comp_top_free, comp_top_auto, comp_standard_auto]
menu_items[editorstate.get_compositing_mode()].set_active(True)
comp_top_free.connect("toggled", lambda w: projectaction.change_current_sequence_compositing_mode(w, appconsts.COMPOSITING_MODE_TOP_DOWN_FREE_MOVE))
comp_top_auto.connect("toggled", lambda w: projectaction.change_current_sequence_compositing_mode(w, appconsts.COMPOSITING_MODE_TOP_DOWN_AUTO_FOLLOW))
comp_standard_auto.connect("toggled", lambda w: projectaction.change_current_sequence_compositing_mode(w, appconsts.COMPOSITING_MODE_STANDARD_AUTO_FOLLOW))
def _init_gui_to_prefs(self):
if editorpersistance.prefs.tabs_on_top == True:
self.notebook.set_tab_pos(Gtk.PositionType.TOP)
else:
self.notebook.set_tab_pos(Gtk.PositionType.BOTTOM)
def _change_windows_preference(self, widget, new_window_layout):
if widget.get_active() == False:
return
editorpersistance.prefs.global_layout = new_window_layout
editorpersistance.save()
primary_txt = _("Global Window Mode changed")
secondary_txt = _("Application restart required for the new layout choice to take effect.")
dialogutils.info_message(primary_txt, secondary_txt, self.window)
def _show_tabs_up(self, widget):
if widget.get_active() == False:
return
self.notebook.set_tab_pos(Gtk.PositionType.TOP)
editorpersistance.prefs.tabs_on_top = True
editorpersistance.save()
def _show_tabs_down(self, widget):
if widget.get_active() == False:
return
self.notebook.set_tab_pos(Gtk.PositionType.BOTTOM)
editorpersistance.prefs.tabs_on_top = False
editorpersistance.save()
def _show_vu_meter(self, widget):
editorpersistance.prefs.show_vu_meter = widget.get_active()
editorpersistance.save()
self._update_top_row(True)
def _update_top_row(self, show_all=False):
self.top_row_hbox.pack_end(audiomonitoring.get_master_meter(), False, False, 0)
if show_all:
self.window.show_all()
def _create_monitor_buttons(self):
self.monitor_switch = guicomponents.MonitorSwitch(self._monitor_switch_handler)
def _monitor_switch_handler(self, action):
if action == appconsts.MONITOR_TLINE_BUTTON_PRESSED:
updater.display_sequence_in_monitor()
if action == appconsts.MONITOR_CLIP_BUTTON_PRESSED:
updater.display_clip_in_monitor()
def connect_player(self, mltplayer):
# Buttons
# NOTE: ORDER OF CALLBACKS IS THE SAME AS ORDER OF BUTTONS FROM LEFT TO RIGHT
# Jul-2016 - SvdB - For play/pause button
if editorpersistance.prefs.play_pause == False:
pressed_callback_funcs = [monitorevent.prev_pressed,
monitorevent.next_pressed,
monitorevent.play_pressed,
monitorevent.stop_pressed,
monitorevent.mark_in_pressed,
monitorevent.mark_out_pressed,
monitorevent.marks_clear_pressed,
monitorevent.to_mark_in_pressed,
monitorevent.to_mark_out_pressed]
else:
pressed_callback_funcs = [monitorevent.prev_pressed,
monitorevent.next_pressed,
monitorevent.play_pressed,
monitorevent.mark_in_pressed,
monitorevent.mark_out_pressed,
monitorevent.marks_clear_pressed,
monitorevent.to_mark_in_pressed,
monitorevent.to_mark_out_pressed]
self.player_buttons.set_callbacks(pressed_callback_funcs)
# Monitor position bar
self.pos_bar.set_listener(mltplayer.seek_position_normalized)
def _get_edit_buttons_row(self):
tools_pixbufs = [INSERTMOVE_CURSOR, OVERWRITE_CURSOR, ONEROLL_CURSOR, ONEROLL_RIPPLE_CURSOR, \
TWOROLL_CURSOR, SLIDE_CURSOR, MULTIMOVE_CURSOR, OVERWRITE_BOX_CURSOR, CUT_CURSOR, KF_TOOL_CURSOR, MULTI_TRIM_CURSOR]
middlebar.create_edit_buttons_row_buttons(self, tools_pixbufs)
buttons_row = Gtk.HBox(False, 1)
if editorpersistance.prefs.midbar_layout == appconsts.MIDBAR_COMPONENTS_CENTERED:
middlebar.fill_with_COMPONENTS_CENTERED_pattern(buttons_row, self)
elif editorpersistance.prefs.midbar_layout == appconsts.MIDBAR_TC_LEFT:
middlebar.fill_with_TC_LEFT_pattern(buttons_row, self)
else:
middlebar.fill_with_TC_MIDDLE_pattern(buttons_row, self)
# Aug-2019 - SvdB - BB
offset = 2
if editorpersistance.prefs.double_track_hights:
offset = 4
buttons_row.set_margin_bottom(offset)
buttons_row.set_margin_top(offset)
buttons_row.set_margin_left(offset)
buttons_row.set_margin_right(offset)
return buttons_row
def _add_tool_tips(self):
self.big_TC.set_tooltip_text(_("Timeline current frame timecode"))
self.view_mode_select.widget.set_tooltip_text(_("Select view mode: Video / Vectorscope/ RGBParade"))
self.trim_view_select.widget.set_tooltip_text(_("Set trim view and match frames"))
self.pos_bar.widget.set_tooltip_text(_("Sequence / Media current position"))
def set_default_edit_tool(self):
# First active tool is the default tool. So we need to always have atleast one tool available.
self.change_tool(editorpersistance.prefs.active_tools[0])
def kf_tool_exit_to_mode(self, mode): # Kf tool can be entered from popup menu and it exists to mode it was started in.
tool_id = None
if mode == editorstate.INSERT_MOVE:
tool_id = appconsts.TLINE_TOOL_INSERT
elif mode == editorstate.OVERWRITE_MOVE:
if editorstate.overwrite_mode_box == False:
tool_id = appconsts.TLINE_TOOL_OVERWRITE
else:
tool_id = appconsts.TLINE_TOOL_BOX
elif mode == editorstate.ONE_ROLL_TRIM or mode == editorstate.ONE_ROLL_TRIM_NO_EDIT:
if editorstate.trim_mode_ripple == False: # this was not touched on entering KF tool
tool_id = appconsts.TLINE_TOOL_TRIM
else:
tool_id = appconsts.TLINE_TOOL_RIPPLE_TRIM
elif mode == editorstate.TWO_ROLL_TRIM or mode == editorstate.TWO_ROLL_TRIM_NO_EDIT:
tool_id = appconsts.TLINE_TOOL_ROLL
elif mode == editorstate.SLIDE_TRIM or mode == editorstate.SLIDE_TRIM_NO_EDIT:
tool_id = appconsts.TLINE_TOOL_SLIP
elif mode == editorstate.MULTI_MOVE:
tool_id = appconsts.TLINE_TOOL_SPACER
elif mode == editorstate.CUT:
tool_id = appconsts.TLINE_TOOL_CUT
elif mode == editorstate.KF_TOOL:
tool_id = appconsts.TLINE_TOOL_KFTOOL
elif mode == editorstate.MULTI_TRIM:
tool_id = appconsts.TLINE_TOOL_MULTI_TRIM
if tool_id != None:
self.change_tool(tool_id)
else:
print("kf_tool_exit_to_mode(): NO TOOL_ID!") # This should not happen, but lets print info instead of crashing if we get here
def change_tool(self, tool_id):
if tool_id == appconsts.TLINE_TOOL_INSERT:
self.handle_insert_move_mode_button_press()
elif tool_id == appconsts.TLINE_TOOL_OVERWRITE:
self.handle_over_move_mode_button_press()
elif tool_id == appconsts.TLINE_TOOL_TRIM:
self.handle_one_roll_mode_button_press()
elif tool_id == appconsts.TLINE_TOOL_ROLL:
self.handle_two_roll_mode_button_press()
elif tool_id == appconsts.TLINE_TOOL_SLIP:
self.handle_slide_mode_button_press()
elif tool_id == appconsts.TLINE_TOOL_SPACER:
self.handle_multi_mode_button_press()
elif tool_id == appconsts.TLINE_TOOL_BOX:
self.handle_box_mode_button_press()
elif tool_id == appconsts.TLINE_TOOL_RIPPLE_TRIM:
self.handle_one_roll_ripple_mode_button_press()
elif tool_id == appconsts.TLINE_TOOL_CUT:
self.handle_cut_mode_button_press()
elif tool_id == appconsts.TLINE_TOOL_KFTOOL:
self.handle_kftool_mode_button_press()
elif tool_id == appconsts.TLINE_TOOL_MULTI_TRIM:
self.handle_multitrim_mode_button_press()
else:
# We should not hit this
print("editorwindow.change_tool() else: hit!")
return
gui.editor_window.set_tool_selector_to_mode()
def handle_over_move_mode_button_press(self):
modesetting.overwrite_move_mode_pressed()
self.set_cursor_to_mode()
def handle_box_mode_button_press(self):
modesetting.box_mode_pressed()
self.set_cursor_to_mode()
def handle_insert_move_mode_button_press(self):
modesetting.insert_move_mode_pressed()
self.set_cursor_to_mode()
def handle_one_roll_mode_button_press(self):
editorstate.trim_mode_ripple = False
modesetting.oneroll_trim_no_edit_init()
self.set_cursor_to_mode()
def handle_one_roll_ripple_mode_button_press(self):
editorstate.trim_mode_ripple = True
modesetting.oneroll_trim_no_edit_init()
self.set_cursor_to_mode()
def handle_two_roll_mode_button_press(self):
modesetting.tworoll_trim_no_edit_init()
self.set_cursor_to_mode()
def handle_slide_mode_button_press(self):
modesetting.slide_trim_no_edit_init()
self.set_cursor_to_mode()
def handle_multi_mode_button_press(self):
modesetting.multi_mode_pressed()
self.set_cursor_to_mode()
def handle_cut_mode_button_press(self):
modesetting.cut_mode_pressed()
self.set_cursor_to_mode()
def handle_kftool_mode_button_press(self):
modesetting.kftool_mode_pressed()
self.set_cursor_to_mode()
def handle_multitrim_mode_button_press(self):
modesetting.multitrim_mode_pressed()
self.set_cursor_to_mode()
def toggle_trim_ripple_mode(self):
editorstate.trim_mode_ripple = (editorstate.trim_mode_ripple == False)
modesetting.stop_looping()
editorstate.edit_mode = editorstate.ONE_ROLL_TRIM_NO_EDIT
tlinewidgets.set_edit_mode(None, None)
self.set_tool_selector_to_mode()
self.set_tline_cursor(editorstate.EDIT_MODE())
updater.set_trim_mode_gui()
def mode_selector_pressed(self, selector, event):
workflow.get_tline_tool_popup_menu(selector, event, self.tool_selector_item_activated)
def tool_selector_item_activated(self, selector, tool):
if tool == appconsts.TLINE_TOOL_INSERT:
self.handle_insert_move_mode_button_press()
if tool == appconsts.TLINE_TOOL_OVERWRITE:
self.handle_over_move_mode_button_press()
if tool == appconsts.TLINE_TOOL_TRIM:
self.handle_one_roll_mode_button_press()
if tool == appconsts.TLINE_TOOL_RIPPLE_TRIM:
self.handle_one_roll_ripple_mode_button_press()
if tool == appconsts.TLINE_TOOL_ROLL:
self.handle_two_roll_mode_button_press()
if tool == appconsts.TLINE_TOOL_SLIP:
self.handle_slide_mode_button_press()
if tool == appconsts.TLINE_TOOL_SPACER:
self.handle_multi_mode_button_press()
if tool == appconsts.TLINE_TOOL_BOX:
self.handle_box_mode_button_press()
if tool == appconsts.TLINE_TOOL_CUT:
self.handle_cut_mode_button_press()
if tool == appconsts.TLINE_TOOL_KFTOOL:
self.handle_kftool_mode_button_press()
if tool == appconsts.TLINE_TOOL_MULTI_TRIM:
self.handle_multitrim_mode_button_press()
self.set_cursor_to_mode()
self.set_tool_selector_to_mode()
def set_cursor_to_mode(self):
if editorstate.cursor_on_tline == True:
self.set_tline_cursor(editorstate.EDIT_MODE())
else:
gdk_window = gui.tline_display.get_parent_window();
gdk_window.set_cursor(Gdk.Cursor.new(Gdk.CursorType.LEFT_PTR))
def get_own_cursor(self, display, surface, hotx, hoty):
pixbuf = Gdk.pixbuf_get_from_surface(surface, 0, 0, surface.get_width(), surface.get_height())
return Gdk.Cursor.new_from_pixbuf(display, pixbuf, hotx, hoty)
def set_tline_cursor(self, mode):
display = Gdk.Display.get_default()
gdk_window = self.window.get_window()
if mode == editorstate.INSERT_MOVE:
cursor = self.get_own_cursor(display, INSERTMOVE_CURSOR, 0, 0)
elif mode == editorstate.OVERWRITE_MOVE:
if editorstate.overwrite_mode_box == False:
cursor = self.get_own_cursor(display, OVERWRITE_CURSOR, 0, 0)
else:
if boxmove.entered_from_overwrite == False:
cursor = self.get_own_cursor(display, OVERWRITE_BOX_CURSOR, 6, 15)
else:
cursor = self.get_own_cursor(display, OVERWRITE_CURSOR, 0, 0)
elif mode == editorstate.TWO_ROLL_TRIM:
cursor = self.get_own_cursor(display, TWOROLL_NO_EDIT_CURSOR, 11, 9)
elif mode == editorstate.TWO_ROLL_TRIM_NO_EDIT:
cursor = self.get_own_cursor(display, TWOROLL_NO_EDIT_CURSOR, 11, 9)
elif mode == editorstate.ONE_ROLL_TRIM:
if editorstate.trim_mode_ripple == False:
cursor = self.get_own_cursor(display, ONEROLL_NO_EDIT_CURSOR, 9, 9)
else:
cursor = self.get_own_cursor(display, ONEROLL_RIPPLE_CURSOR, 9, 9)
elif mode == editorstate.ONE_ROLL_TRIM_NO_EDIT:
if editorstate.trim_mode_ripple == False:
cursor = self.get_own_cursor(display, ONEROLL_NO_EDIT_CURSOR, 9, 9)
else:
cursor = self.get_own_cursor(display, ONEROLL_RIPPLE_CURSOR, 9, 9)
elif mode == editorstate.SLIDE_TRIM:
cursor = self.get_own_cursor(display, SLIDE_NO_EDIT_CURSOR, 9, 9)
elif mode == editorstate.SLIDE_TRIM_NO_EDIT:
cursor = self.get_own_cursor(display, SLIDE_NO_EDIT_CURSOR, 9, 9)
elif mode == editorstate.SELECT_PARENT_CLIP:
cursor = Gdk.Cursor.new(Gdk.CursorType.TCROSS)
elif mode == editorstate.SELECT_TLINE_SYNC_CLIP:
cursor = Gdk.Cursor.new(Gdk.CursorType.TCROSS)
elif mode == editorstate.MULTI_MOVE:
cursor = self.get_own_cursor(display, MULTIMOVE_CURSOR, 4, 8)
elif mode == editorstate.CLIP_END_DRAG:
surface, px, py = self.context_cursors[tlinewidgets.pointer_context]
cursor = self.get_own_cursor(display, surface, px, py)
elif mode == editorstate.CUT:
cursor = self.get_own_cursor(display, CUT_CURSOR, 1, 8)
elif mode == editorstate.KF_TOOL:
cursor = self.get_own_cursor(display, KF_TOOL_CURSOR, 1, 0)
elif mode == editorstate.MULTI_TRIM:
cursor = self.get_own_cursor(display, MULTI_TRIM_CURSOR, 1, 0)
else:
cursor = Gdk.Cursor.new(Gdk.CursorType.LEFT_PTR)
gdk_window.set_cursor(cursor)
def set_tline_cursor_to_context(self, pointer_context):
display = Gdk.Display.get_default()
gdk_window = self.window.get_window()
surface, px, py = self.context_cursors[pointer_context]
cursor = self.get_own_cursor(display, surface, px, py)
gdk_window.set_cursor(cursor)
def set_tool_selector_to_mode(self):
if editorstate.EDIT_MODE() == editorstate.INSERT_MOVE:
self.tool_selector.set_tool_pixbuf(appconsts.TLINE_TOOL_INSERT)
elif editorstate.EDIT_MODE() == editorstate.OVERWRITE_MOVE:
if editorstate.overwrite_mode_box == False:
self.tool_selector.set_tool_pixbuf(appconsts.TLINE_TOOL_OVERWRITE)
else:
self.tool_selector.set_tool_pixbuf(appconsts.TLINE_TOOL_BOX)
elif editorstate.EDIT_MODE() == editorstate.ONE_ROLL_TRIM or editorstate.EDIT_MODE() == editorstate.ONE_ROLL_TRIM_NO_EDIT:
if editorstate.trim_mode_ripple == False:
self.tool_selector.set_tool_pixbuf(appconsts.TLINE_TOOL_TRIM)
else:
self.tool_selector.set_tool_pixbuf(appconsts.TLINE_TOOL_RIPPLE_TRIM)
elif editorstate.EDIT_MODE() == editorstate.TWO_ROLL_TRIM:
self.tool_selector.set_tool_pixbuf(appconsts.TLINE_TOOL_ROLL)
elif editorstate.EDIT_MODE() == editorstate.TWO_ROLL_TRIM_NO_EDIT:
self.tool_selector.set_tool_pixbuf(appconsts.TLINE_TOOL_ROLL)
elif editorstate.EDIT_MODE() == editorstate.SLIDE_TRIM:
self.tool_selector.set_tool_pixbuf(appconsts.TLINE_TOOL_SLIP)
elif editorstate.EDIT_MODE() == editorstate.SLIDE_TRIM_NO_EDIT:
self.tool_selector.set_tool_pixbuf(appconsts.TLINE_TOOL_SLIP)
elif editorstate.EDIT_MODE() == editorstate.MULTI_MOVE:
self.tool_selector.set_tool_pixbuf(appconsts.TLINE_TOOL_SPACER)
elif editorstate.EDIT_MODE() == editorstate.CUT:
self.tool_selector.set_tool_pixbuf(appconsts.TLINE_TOOL_CUT)
elif editorstate.EDIT_MODE() == editorstate.KF_TOOL:
self.tool_selector.set_tool_pixbuf(appconsts.TLINE_TOOL_KFTOOL)
elif editorstate.EDIT_MODE() == editorstate.MULTI_TRIM:
self.tool_selector.set_tool_pixbuf(appconsts.TLINE_TOOL_MULTI_TRIM)
def tline_cursor_leave(self, event):
cursor = Gdk.Cursor.new(Gdk.CursorType.LEFT_PTR)
gdk_window = self.window.get_window()
gdk_window.set_cursor(cursor)
if event.get_state() & Gdk.ModifierType.BUTTON1_MASK:
if editorstate.current_is_move_mode():
tlineaction.mouse_dragged_out(event)
def tline_cursor_enter(self, event):
editorstate.cursor_on_tline = True
self.set_cursor_to_mode()
def top_paned_resized(self, w, req):
print(self.app_v_paned.get_position())
print(self.top_paned.get_position())
print(self.mm_paned.get_position())
def _create_monitor_row_widgets(self):
self.monitor_tc_info = guicomponents.MonitorTCInfo()
def _this_is_not_used():
print("THIS WAS USED!!!!!")
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/exporting.py 0000664 0000000 0000000 00000050235 13610327166 0025501 0 ustar 00root root 0000000 0000000 """
Flowblade Movie Editor is a nonlinear video editor.
Copyright 2012 Janne Liljeblad.
This file is part of Flowblade Movie Editor .
Flowblade Movie Editor 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.
Flowblade Movie Editor is distributed in the hope that 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 Flowblade Movie Editor. If not, see .
"""
from gi.repository import Gtk
import os
from xml.dom import minidom
from math import floor
import mlt
import time
import hashlib
import re
import shutil
import appconsts
import atomicfile
import dialogs
import dialogutils
from editorstate import PLAYER
from editorstate import PROJECT
from editorstate import current_sequence
import exportardour
import gui
import guiutils
import renderconsumer
import utils
import userfolders
REEL_NAME_HASH_8_NUMBER = 1
REEL_NAME_FILE_NAME_START = 2
_xml_render_player = None
_screenshot_img = None
_img_types = ["png", "bmp", "targa","tiff"]
_img_extensions = ["png", "bmp", "tga","tif"]
####---------------MLT--------------####
def MELT_XML_export():
dialogs.export_xml_dialog(_export_melt_xml_dialog_callback, PROJECT().name)
def _export_melt_xml_dialog_callback(dialog, response_id):
if response_id == Gtk.ResponseType.ACCEPT:
filenames = dialog.get_filenames()
save_path = filenames[0]
#global _xml_render_monitor
_xml_render_player = renderconsumer.XMLRenderPlayer(save_path,
_xml_render_done,
None)
_xml_render_player.start()
dialog.destroy()
else:
dialog.destroy()
def _xml_render_done(data):
global _xml_render_player
_xml_render_player = None
####---------------EDL--------------####
def EDL_export():
dialogs.export_edl_dialog(_export_edl_dialog_callback, gui.editor_window.window, PROJECT().name)
def _export_edl_dialog_callback(dialog, response_id):
if response_id == Gtk.ResponseType.ACCEPT:
filenames = dialog.get_filenames()
edl_path = filenames[0]
_xml_render_player = renderconsumer.XMLRenderPlayer(get_edl_temp_xml_path(),
_edl_xml_render_done,
edl_path)
_xml_render_player.start()
dialog.destroy()
else:
dialog.destroy()
def _edl_xml_render_done(data):
edl_path = data
mlt_parse = MLTXMLToEDLParse(get_edl_temp_xml_path(), current_sequence())
edl_contents = mlt_parse.create_edl()
with atomicfile.AtomicFileWriter(edl_path, "w") as afw:
f = afw.get_file()
f.write(edl_contents)
def get_edl_temp_xml_path():
return userfolders.get_cache_dir() + "edl_temp_xml.xml"
class MLTXMLToEDLParse:
def __init__(self, xmlfile, current_sequence):
self.xmldoc = minidom.parse(xmlfile)
self.current_sequence = current_sequence
self.producers = {} # producer id -> producer_data
self.resource_to_reel_name = {}
self.reel_name_to_resource = {}
self.reel_name_type = REEL_NAME_FILE_NAME_START
self.from_clip_comment = True
self.use_drop_frames = False
def get_project_profile(self):
profile_dict = {}
profile = self.xmldoc.getElementsByTagName("profile")
key_list = list(profile.item(0).attributes.keys())
for a in key_list:
profile_dict[a] = profile.item(0).attributes[a].value
return profile_dict
def get_tracks(self):
tracks = []
t = self.xmldoc.getElementsByTagName("track")
for track in t:
tracks.append(track.attributes["producer"].value)
return tuple(tracks)
def get_playlists(self):
playlist_list = []
playlists = self.xmldoc.getElementsByTagName("playlist")
eid = 0
for p in playlists:
track_id_attr_value = p.attributes["id"].value
# Don't empty, black or hidden tracks
if track_id_attr_value == "playlist0":
continue
if len(p.getElementsByTagName("entry")) < 1:
continue
# plist contains id and events list data
plist = {}
plist["pl_id"] = track_id_attr_value
# Set track type info
track_index = int(track_id_attr_value.lstrip("playlist"))
track_object = self.current_sequence.tracks[track_index]
plist["src_channel"] = "AA/V"
if track_object.type == appconsts.AUDIO:
plist["src_channel"] = "AA"
# Create events list
event_list = []
event_nodes = p.childNodes
events = []
for i in range(0, event_nodes.length):
# Get edit event
event_node = event_nodes.item(i)
# Create event and give it id
event = {}
event["eid"] = eid
eid = eid + 1
# Set event data
if event_node.localName == "entry":# or event.localName == "blank":
event["type"] = event_node.localName
event["producer"] = event_node.attributes["producer"].value
event["inTime"] = event_node.attributes["in"].value
event["outTime"] = event_node.attributes["out"].value
event_list.append(event)
elif event_node.localName == "blank":
event["type"] = event_node.localName
event["length"] = event_node.attributes["length"].value
event_list.append(event)
plist["events_list"] = event_list
# Add to playlists list
playlist_list.append(plist)
return tuple(playlist_list)
def create_producers_dict(self):
producer_nodes = self.xmldoc.getElementsByTagName("producer")
for p in producer_nodes:
producer_data = {}
producer_data["id"] = p.attributes["id"].value
producer_data["inTime"] = p.attributes["in"].value
producer_data["outTime"] = p.attributes["out"].value
properties = p.getElementsByTagName("property")
for props in properties:
producer_data[props.attributes["name"].value.replace(".","_")] = props.firstChild.data
self.producers[producer_data["id"]] = producer_data
def link_resources(self):
for producer_id, producer_data in self.producers.items():
producer_resource = producer_data["resource"]
reel_name = self.get_reel_name(producer_resource)
# If two reel names are same but point to different resources,
# use md5 hash as reel name for the new resource.
# This happens when two resources have same 8 first letters in file name.
if reel_name in self.reel_name_to_resource:
existing_resource = self.reel_name_to_resource[reel_name]
if existing_resource != producer_resource:
reel_name = hashlib.md5(producer_resource.encode('utf-8')).hexdigest()[:8]
self.resource_to_reel_name[producer_resource] = reel_name
self.reel_name_to_resource[reel_name] = producer_resource
def get_reel_name(self, resource):
if self.reel_name_type == REEL_NAME_HASH_8_NUMBER:
return "{0:08d}".format(hashlib.md5(resource.encode('utf-8')).hexdigest())
else:
file_name = resource.split("/")[-1]
file_name_no_ext = file_name.split(".")[0]
file_name_no_ext = re.sub('[^0-9a-zA-Z]+', 'X', file_name_no_ext)
file_name_len = len(file_name_no_ext)
if file_name_len >= 8:
reel_name = file_name_no_ext[0:8]
else:
reel_name = file_name_no_ext + "XXXXXXXX"[0:8 - file_name_len]
return reel_name
def get_producer_media_data(self, producer_id):
producer_data = self.producers[producer_id]
producer_resource = producer_data["resource"]
reel_name = self.resource_to_reel_name[producer_resource]
return reel_name, producer_resource
def create_edl(self):
self.create_producers_dict()
self.link_resources()
playlists = self.get_playlists()
edl_event_count = 1 # incr. event index
str_list = []
for plist in playlists:
prog_in = 0
prog_out = 0
str_list.append("\n === " + plist["pl_id"] + " === \n\n")
event_list = plist["events_list"]
src_channel = plist["src_channel"]
for event in event_list:
if event["type"] == "entry":
src_in = int(event["inTime"])
src_out = int(event["outTime"])
src_len = src_out - src_in + 1
prog_out = prog_in + src_len
producer_id = event["producer"]
reel_name, resource = self.get_producer_media_data(producer_id)
elif event["type"] == "blank":
src_in = 0
src_out = int(event["length"])
src_len = int(event["length"])
prog_out = prog_in + int(event["length"])
reel_name = "BL "
resource = None
src_transition = "C"
str_list.append("{0:03d}".format(edl_event_count))
str_list.append(" ")
str_list.append(reel_name)
str_list.append(" ")
str_list.append(src_channel)
str_list.append(" ")
str_list.append(src_transition)
str_list.append(" ")
str_list.append(self.frames_to_tc(src_in))
str_list.append(" ")
str_list.append(self.frames_to_tc(src_out + 1))
str_list.append(" ")
str_list.append(self.frames_to_tc(prog_in))
str_list.append(" ")
str_list.append(self.frames_to_tc(prog_out))
str_list.append("\n")
if self.from_clip_comment == True and resource != None:
str_list.append("* FROM CLIP NAME: " + resource.split("/")[-1] + "\n")
edl_event_count += 1;
prog_in += src_len
#print ''.join(str_list).strip("\n")
return ''.join(str_list).strip("\n")
def frames_to_tc(self, frame):
if self.use_drop_frames == True:
return self.frames_to_DF(frame)
else:
return utils.get_tc_string(frame)
def frames_to_DF(self, framenumber):
"""
This method adapted from C++ code called "timecode" by Jason Wood.
begin: Wed Dec 17 2003
copyright: (C) 2003 by Jason Wood
email: jasonwood@blueyonder.co.uk
Framerate should be 29.97, 59.94, or 23.976, otherwise the calculations will be off.
"""
projectMeta = self.get_project_profile()
framerate = float(projectMeta["frame_rate_num"]) / float(projectMeta["frame_rate_den"])
# Number of frames to drop on the minute marks is the nearest integer to 6% of the framerate
dropFrames = round(framerate * 0.066666)
# Number of frames in an hour
framesPerHour = round(framerate * 60 * 60)
# Number of frames in a day - timecode rolls over after 24 hours
framesPerDay = framesPerHour * 24
# Number of frames per ten minutes
framesPer10Minutes = round(framerate * 60 * 10)
# Number of frames per minute is the round of the framerate * 60 minus the number of dropped frames
framesPerMinute = (round(framerate) * 60) - dropFrames
if (framenumber < 0): # For negative time, add 24 hours.
framenumber = framesPerDay + framenumber
# If framenumber is greater than 24 hrs, next operation will rollover clock
# % is the modulus operator, which returns a remainder. a % b = the remainder of a/b
framenumber = framenumber % framesPerDay
d = floor(framenumber / framesPer10Minutes)
m = framenumber % framesPer10Minutes
if (m > 1):
framenumber=framenumber + (dropFrames * 9 * d) + dropFrames * floor((m-dropFrames) / framesPerMinute)
else:
framenumber = framenumber + dropFrames * 9 * d;
frRound = round(framerate);
frames = framenumber % frRound;
seconds = floor(framenumber / frRound) % 60;
minutes = floor(floor(framenumber / frRound) / 60) % 60;
hours = floor(floor(floor(framenumber / frRound) / 60) / 60);
tc = "%d:%02d:%02d;%02d" % (hours, minutes, seconds, frames)
return tc
####---------------Screenshot--------------####
def screenshot_export():
length = current_sequence().tractor.get_length()
if length < 2:
dialogutils.info_message(_("Sequence is too short"), _("Sequence needs to be at least 2 frames long to allow frame export."), None)
return
frame = PLAYER().current_frame()
# Can't get last frame to render easily, so just force range.
if frame > length - 2:
frame = length - 2
render_screen_shot(frame, get_displayed_image_render_path(), "png")
export_screenshot_dialog(_export_screenshot_dialog_callback, frame,
gui.editor_window.window, PROJECT().name)
PLAYER().seek_frame(frame)
def _export_screenshot_dialog_callback(dialog, response_id, data):
file_name, out_folder, file_type_combo, frame = data
if response_id == Gtk.ResponseType.YES:
vcodec = _img_types[file_type_combo.get_active()]
ext = _img_extensions[file_type_combo.get_active()]
render_path = userfolders.get_hidden_screenshot_dir_path() + "screenshot_%01d." + ext
rendered_file_path = userfolders.get_hidden_screenshot_dir_path() + "screenshot_1." + ext
out_file_path = out_folder.get_filename()+ "/" + file_name.get_text() + "." + ext
dialog.destroy()
render_screen_shot(frame, render_path, vcodec)
shutil.copyfile(rendered_file_path, out_file_path)
else:
dialog.destroy()
purge_screenshots()
PLAYER().seek_frame(frame)
def get_displayed_image_render_path():
return userfolders.get_hidden_screenshot_dir_path() + "screenshot_%01d.png"
def get_displayed_image_path():
return userfolders.get_hidden_screenshot_dir_path() + "screenshot_1.png"
def render_screen_shot(frame, render_path, vcodec):
producer = current_sequence().tractor
consumer = mlt.Consumer(PROJECT().profile, "avformat", str(render_path))
consumer.set("real_time", -1)
consumer.set("rescale", "bicubic")
consumer.set("vcodec", str(vcodec))
renderer = renderconsumer.FileRenderPlayer(None, producer, consumer, frame, frame + 1)
renderer.wait_for_producer_end_stop = False
renderer.consumer_pos_stop_add = 2 # Hack, see FileRenderPlayer
renderer.start()
while renderer.has_started_running == False:
time.sleep(0.05)
while renderer.stopped == False:
time.sleep(0.05)
def export_screenshot_dialog(callback, frame, parent_window, project_name):
cancel_str = _("Cancel")
ok_str = _("Export Image")
dialog = Gtk.Dialog(_("Export Frame Image"),
parent_window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(cancel_str, Gtk.ResponseType.CANCEL,
ok_str, Gtk.ResponseType.YES))
global _screenshot_img
_screenshot_img = guiutils.get_gtk_image_from_file(get_displayed_image_path(), 300)
frame_frame = guiutils.get_named_frame_with_vbox(None, [_screenshot_img])
INPUT_LABELS_WITDH = 320
project_name = project_name.strip(".flb")
file_name = Gtk.Entry()
file_name.set_text(project_name)
extension_label = Gtk.Label(label=".png")
extension_label.set_size_request(35, 20)
name_pack = Gtk.HBox(False, 4)
name_pack.pack_start(file_name, True, True, 0)
name_pack.pack_start(extension_label, False, False, 0)
name_row = guiutils.get_two_column_box(Gtk.Label(label=_("Export file name:")), name_pack, INPUT_LABELS_WITDH)
out_folder = Gtk.FileChooserButton(_("Select target folder"))
out_folder.set_action(Gtk.FileChooserAction.SELECT_FOLDER)
out_folder.set_current_folder(os.path.expanduser("~") + "/")
folder_row = guiutils.get_two_column_box(Gtk.Label(label=_("Export folder:")), out_folder, INPUT_LABELS_WITDH)
file_type_combo = Gtk.ComboBoxText()
for img in _img_types:
file_type_combo.append_text(img)
file_type_combo.set_active(0)
file_type_combo.connect("changed", _file_type_changed, extension_label)
file_type_row = guiutils.get_two_column_box(Gtk.Label(label=_("Image type:")), file_type_combo, INPUT_LABELS_WITDH)
file_frame = guiutils.get_named_frame_with_vbox(None, [file_type_row, name_row, folder_row])
vbox = Gtk.VBox(False, 2)
vbox.pack_start(frame_frame, False, False, 0)
vbox.pack_start(guiutils.pad_label(12, 12), False, False, 0)
vbox.pack_start(file_frame, False, False, 0)
alignment = guiutils.set_margins(vbox, 12, 12, 12, 12)
dialog.vbox.pack_start(alignment, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
dialogutils.default_behaviour(dialog)
dialog.connect('response', callback, (file_name, out_folder, file_type_combo, frame)) #(file_name, out_folder, track_select_combo, cascade_check, op_combo, audio_track_select_combo))
dialog.show_all()
def _file_type_changed(combo, label):
label.set_text("." + _img_extensions[combo.get_active()])
def purge_screenshots():
d = userfolders.get_hidden_screenshot_dir_path()
for f in os.listdir(d):
os.remove(os.path.join(d, f))
####---------------Ardour Session--------------####
def ardour_export():
print ("Ardour export...")
dialogs.export_ardour_session_folder_select(_ardour_export_dialog_callback)
def _ardour_export_dialog_callback(dialog, response_id, session_folder):
if response_id != Gtk.ResponseType.ACCEPT:
dialog.destroy()
return
folder_path = session_folder.get_filenames()[0]
if not (os.listdir(folder_path) == []):
dialog.destroy()
primary_txt = _("Selected folder contains files")
secondary_txt = _("When exporting audio to Ardour, the selected folder\nhas to be empty.")
dialogutils.info_message(primary_txt, secondary_txt, gui.editor_window.window)
return
# name = name_entry.get_text()
dialog.destroy()
xml_save_path = userfolders.get_cache_dir() + "ardour_export.xml"
global _xml_render_player
_xml_render_player = renderconsumer.XMLRenderPlayer(xml_save_path,
_ardour_xml_render_done,
None)
_xml_render_player.ardour_session_folder = folder_path
_xml_render_player.start()
def _ardour_xml_render_done(data):
global _xml_render_player
adour_session_folder = _xml_render_player.ardour_session_folder
temp_xml = _xml_render_player.file_name
_xml_render_player = None
exportardour.use_existing_basedir = True
exportardour.launch_export_ardour_session_from_flowblade(temp_xml, adour_session_folder)
print ("Ardour export done.")
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/extraeditors.py 0000664 0000000 0000000 00000141661 13610327166 0026203 0 ustar 00root root 0000000 0000000 """
Flowblade Movie Editor is a nonlinear video editor.
Copyright 2014 Janne Liljeblad.
This file is part of Flowblade Movie Editor .
Flowblade Movie Editor 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.
Flowblade Movie Editor is distributed in the hope that 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 Flowblade Movie Editor. If not, see .
"""
"""
This module contains complex property editors.
"""
import math
from gi.repository import Gtk
import appconsts
import cairo
import cairoarea
import editorpersistance
import guiutils
import guicomponents
import glassbuttons
import lutfilter
import respaths
import translations
SHADOW = 0
MID = 1
HI = 2
NO_HIT = 99
SELECT_CIRCLE = 0
SELECT_LINE = 1
ACTIVE_RING_COLOR = (0.0, 0.0, 0.0)
DEACTIVE_RING_COLOR = (0.6, 0.6, 0.6)
ACTIVE_SHADOW_COLOR = (0.15, 0.15, 0.15)
ACTIVE_MID_COLOR = (0.5, 0.5, 0.5)
ACTIVE_HI_COLOR = (1.0, 1.0, 1.0)
DEACTIVE_SHADOW_COLOR = (0.6, 0.6, 0.6)
DEACTIVE_MID_COLOR = (0.7, 0.7, 0.7)
DEACTIVE_HI_COLOR = (0.85, 0.85, 0.85)
BOX_BG_COLOR = (0.8, 0.8, 0.8)
BOX_LINE_COLOR = (0.4, 0.4, 0.4)
CURVE_COLOR = (0, 0, 0)
R_CURVE_COLOR = (0.78, 0, 0)
G_CURVE_COLOR = (0, 0.75, 0)
B_CURVE_COLOR = (0, 0, 0.8)
RED_STOP = (0, 1, 0, 0, 1)
YELLOW_STOP = (1.0/6.0, 1, 1, 0, 1)
GREEN_STOP = (2.0/6.0, 0, 1, 0, 1)
CYAN_STOP = (3.0/6.0, 0, 1, 1, 1)
BLUE_STOP = (4.0/6.0, 0, 0, 1, 1)
MAGENTA_STOP = (5.0/6.0, 1, 0, 1, 1)
RED_STOP_END = (1, 1, 0, 0, 1)
GREY_GRAD_1 = (1, 0.4, 0.4, 0.4, 1)
GREY_GRAD_2 = (0, 0.4, 0.4, 0.4, 0)
MID_GREY_GRAD_1 = (1, 0.3, 0.3, 0.3, 0)
MID_GREY_GRAD_2 = (0.5, 0.3, 0.3, 0.3, 1)
MID_GREY_GRAD_3 = (0, 0.3, 0.3, 0.3, 0)
CIRCLE_GRAD_1 = (1, 0.3, 0.3, 0.3, 1)
CIRCLE_GRAD_2 = (0, 0.8, 0.8, 0.8, 1)
FX_GRAD_1 = (0, 1.0, 1.0, 1.0, 0.4)
FX_GRAD_2 = (1, 0.3, 0.3, 0.3, 0.4)
def _p(name):
try:
return translations.param_names[name]
except KeyError:
return name
def _draw_select_circle(cr, x, y, main_color, radius, small_radius, pad, x_off=0, y_off=0):
degrees = math.pi / 180.0
grad = cairo.LinearGradient (x, y, x, y + 2 * radius)
grad.add_color_stop_rgba(*CIRCLE_GRAD_1)
grad.add_color_stop_rgba(*CIRCLE_GRAD_2)
cr.set_source(grad)
cr.move_to(x + pad, y + pad)
cr.arc (x + pad, y + pad, radius, 0.0 * degrees, 360.0 * degrees)
cr.fill()
cr.set_source_rgb(*main_color)
cr.move_to(x + pad, y + pad)
cr.arc (x + pad, y + pad, small_radius, 0.0 * degrees, 360.0 * degrees)
cr.fill()
grad = cairo.LinearGradient (x, y, x, y + 2 * radius)
grad.add_color_stop_rgba(*FX_GRAD_1)
grad.add_color_stop_rgba(*FX_GRAD_2)
cr.set_source(grad)
cr.move_to(x + pad, y + pad)
cr.arc (x + pad, y + pad, small_radius, 0.0 * degrees, 360.0 * degrees)
cr.fill()
x = x + x_off
y = y + y_off
cr.set_source_rgb(0.4,0.4,0.4)
cr.set_line_width(1.0)
cr.move_to(x + radius - 0.5, y)
cr.line_to(x + radius - 0.5, y + 2 * radius)
cr.stroke()
cr.set_source_rgb(0.4,0.4,0.4)
cr.set_line_width(1.0)
cr.move_to(x, y + radius - 0.5)
cr.line_to(x + 2 * radius, y + radius - 0.5)
cr.stroke()
cr.set_source_rgb(0.6,0.6,0.6)
cr.move_to(x, y + radius + 0.5)
cr.line_to(x + radius * 2.0, y + radius + 0.5)
cr.stroke()
cr.set_source_rgb(0.6,0.6,0.6)
cr.move_to(x + radius + 0.5, y)
cr.line_to(x + radius + 0.5, y + 2 * radius)
cr.stroke()
def _draw_select_line(cr, x, y):
height = 22
y = y - 19
cr.set_source_rgb(0.7,0.7,0.7)
cr.rectangle(x - 2.0, y, 4, height)
cr.fill()
cr.set_source_rgb(0.3,0.3,0.3)
cr.set_line_width(1.0)
cr.move_to(x - 0.5, y)
cr.line_to(x - 0.5, y + height)
cr.stroke()
cr.set_source_rgb(0.95,0.95,0.95)
cr.move_to(x + 0.5, y)
cr.line_to(x + 0.5, y + height)
cr.stroke()
def _draw_cursor_indicator(cr, x, y, radius):
degrees = math.pi / 180.0
pad = radius
cr.set_source_rgba(0.9, 0.9, 0.9, 0.6)
cr.set_line_width(3.0)
cr.arc (x + pad, y + pad, radius, 0.0 * degrees, 360.0 * degrees)
cr.stroke()
class ColorBox:
def __init__(self, edit_listener, width=260, height=260):
self.W = width
self.H = height
self.widget = cairoarea.CairoDrawableArea2( self.W,
self.H,
self._draw)
self.widget.press_func = self._press_event
self.widget.motion_notify_func = self._motion_notify_event
self.widget.release_func = self._release_event
self.X_PAD = 12
self.Y_PAD = 12
self.CIRCLE_HALF = 8
self.cursor_x = self.X_PAD
self.cursor_y = self.H - self.Y_PAD
self.edit_listener = edit_listener
self.hue = 0.0
self.saturation = 0.0
self.draw_saturation_gradient = True
self.selection_cursor = SELECT_CIRCLE
def get_hue_saturation(self):
return (self.hue, self.saturation)
def _save_values(self):
self.hue = float((self.cursor_x - self.X_PAD)) / float((self.W - 2 * self.X_PAD))
self.saturation = float(abs(self.cursor_y - self.H + self.Y_PAD)) / float((self.H - 2 * self.Y_PAD))
def set_cursor(self, hue, saturation):
self.cursor_x = self._x_for_hue(hue)
self.cursor_y = self._y_for_saturation(saturation)
self._save_values()
def _x_for_hue(self, hue):
return self.X_PAD + hue * (self.W - self.X_PAD * 2)
def _y_for_saturation(self, saturation):
return self.Y_PAD + (1.0 - saturation) * (self.H - self.Y_PAD *2)
def _press_event(self, event):
self.cursor_x, self.cursor_y = self._get_legal_point(event.x, event.y)
self._save_values()
self.edit_listener()
self.widget.queue_draw()
def _motion_notify_event(self, x, y, state):
self.cursor_x, self.cursor_y = self._get_legal_point(x, y)
self._save_values()
self.edit_listener()
self.widget.queue_draw()
def _release_event(self, event):
self.cursor_x, self.cursor_y = self._get_legal_point(event.x, event.y)
self._save_values()
self.edit_listener()
self.widget.queue_draw()
def _get_legal_point(self, x, y):
if x < self.X_PAD:
x = self.X_PAD
elif x > self.W - self.X_PAD:
x = self.W - self.X_PAD
if y < self.Y_PAD:
y = self.Y_PAD
elif y > self.H - self.Y_PAD:
y = self.H - self.Y_PAD
return (x, y)
def _draw(self, event, cr, allocation):
"""
Callback for repaint from CairoDrawableArea.
We get cairo context and allocation.
"""
x, y, w, h = allocation
# Draw bg
#cr.set_source_rgb(*guiutils.get_theme_bg_color())
#cr.rectangle(0, 0, w, h)
#cr.fill()
x_in = self.X_PAD
x_out = self.W - self.X_PAD
y_in = self.Y_PAD
y_out = self.H - self.Y_PAD
grad = cairo.LinearGradient (x_in, 0, x_out, 0)
grad.add_color_stop_rgba(*RED_STOP)
grad.add_color_stop_rgba(*YELLOW_STOP)
grad.add_color_stop_rgba(*GREEN_STOP)
grad.add_color_stop_rgba(*CYAN_STOP)
grad.add_color_stop_rgba(*MAGENTA_STOP)
grad.add_color_stop_rgba(*RED_STOP_END)
cr.set_source(grad)
cr.rectangle(self.X_PAD, self.Y_PAD, x_out - x_in, y_out - y_in)
cr.fill()
if self.draw_saturation_gradient == True:
grey_grad = cairo.LinearGradient (0, y_in, 0, y_out)
grey_grad.add_color_stop_rgba(*GREY_GRAD_1)
grey_grad.add_color_stop_rgba(*GREY_GRAD_2)
cr.set_source(grey_grad)
cr.rectangle(self.X_PAD, self.Y_PAD, x_out - x_in, y_out - y_in)
cr.fill()
if self.selection_cursor == SELECT_CIRCLE:
_draw_select_circle(cr, self.cursor_x - self.CIRCLE_HALF, self.cursor_y - self.CIRCLE_HALF, (1, 1, 1), 8, 6, 8)
else:
_draw_select_line(cr, self.cursor_x, y_out)
class ThreeBandColorBox(ColorBox):
def __init__(self, edit_listener, band_change_listerner, width=260, height=260):
ColorBox.__init__(self, edit_listener, width, height)
self.band = SHADOW
self.shadow_x = self.cursor_x
self.shadow_y = self.cursor_y
self.mid_x = self.cursor_x
self.mid_y = self.cursor_y
self.hi_x = self.cursor_x
self.hi_y = self.cursor_y
self.band_change_listerner = band_change_listerner
def set_cursors(self, s_h, s_s, m_h, m_s, h_h, h_s):
self.shadow_x = self._x_for_hue(s_h)
self.shadow_y = self._y_for_saturation(s_s)
self.mid_x = self._x_for_hue(m_h)
self.mid_y = self._y_for_saturation(m_s)
self.hi_x = self._x_for_hue(h_h)
self.hi_y = self._y_for_saturation(h_s)
def _press_event(self, event):
self.cursor_x, self.cursor_y = self._get_legal_point(event.x, event.y)
hit_value = self._check_band_hit(self.cursor_x, self.cursor_y)
if hit_value != self.band and hit_value != NO_HIT:
self.band = hit_value
self.band_change_listerner(self.band)
self._save_values()
self.edit_listener()
self.widget.queue_draw()
def _motion_notify_event(self, x, y, state):
self.cursor_x, self.cursor_y = self._get_legal_point(x, y)
self._save_values()
self.edit_listener()
self.widget.queue_draw()
def _release_event(self, event):
self.cursor_x, self.cursor_y = self._get_legal_point(event.x, event.y)
self._save_values()
self.edit_listener()
self.widget.queue_draw()
def _check_band_hit(self, x, y):
if self._control_point_hit(x, y, self.shadow_x, self.shadow_y):
return SHADOW
elif self._control_point_hit(x, y, self.mid_x, self.mid_y):
return MID
elif self._control_point_hit(x, y, self.hi_x, self.hi_y):
return HI
else:
return NO_HIT
def _control_point_hit(self, x, y, cx, cy):
if x >= cx - self.CIRCLE_HALF and x <= cx + self.CIRCLE_HALF:
if y >= cy - self.CIRCLE_HALF and y <= cy + self.CIRCLE_HALF:
return True
return False
def _save_values(self):
self.hue = float((self.cursor_x - self.X_PAD)) / float((self.W - 2 * self.X_PAD))
self.saturation = float(abs(self.cursor_y - self.H + self.Y_PAD)) / float((self.H - 2 * self.Y_PAD))
if self.band == SHADOW:
self.shadow_x = self.cursor_x
self.shadow_y = self.cursor_y
elif self.band == MID:
self.mid_x = self.cursor_x
self.mid_y = self.cursor_y
else:
self.hi_x = self.cursor_x
self.hi_y = self.cursor_y
def _draw(self, event, cr, allocation):
"""
Callback for repaint from CairoDrawableArea.
We get cairo context and allocation.
"""
x, y, w, h = allocation
# Draw bg
#cr.set_source_rgb(*guiutils.get_theme_bg_color())
#cr.rectangle(0, 0, w, h)
#cr.fill()
x_in = self.X_PAD
x_out = self.W - self.X_PAD
y_in = self.Y_PAD
y_out = self.H - self.Y_PAD
grad = cairo.LinearGradient (x_in, 0, x_out, 0)
grad.add_color_stop_rgba(*RED_STOP)
grad.add_color_stop_rgba(*YELLOW_STOP)
grad.add_color_stop_rgba(*GREEN_STOP)
grad.add_color_stop_rgba(*CYAN_STOP)
grad.add_color_stop_rgba(*MAGENTA_STOP)
grad.add_color_stop_rgba(*RED_STOP_END)
cr.set_source(grad)
cr.rectangle(self.X_PAD, self.Y_PAD, x_out - x_in, y_out - y_in)
cr.fill()
grey_grad = cairo.LinearGradient (0, y_in, 0, y_out)
grey_grad.add_color_stop_rgba(*MID_GREY_GRAD_1)
grey_grad.add_color_stop_rgba(*MID_GREY_GRAD_2)
grey_grad.add_color_stop_rgba(*MID_GREY_GRAD_3)
cr.set_source(grey_grad)
cr.rectangle(self.X_PAD, self.Y_PAD, x_out - x_in, y_out - y_in)
cr.fill()
y_mid = self.Y_PAD + math.floor((y_out - y_in)/2.0) + 0.2
cr.set_line_width(0.6)
cr.set_source_rgb(0.7,0.7,0.7)
cr.move_to(x_in, y_mid)
cr.line_to(x_out, y_mid)
cr.stroke()
_draw_select_circle(cr, self.shadow_x - self.CIRCLE_HALF, self.shadow_y - self.CIRCLE_HALF, ACTIVE_SHADOW_COLOR, 8, 7, 8)
_draw_select_circle(cr, self.mid_x - self.CIRCLE_HALF, self.mid_y - self.CIRCLE_HALF, ACTIVE_MID_COLOR, 8, 7, 8)
_draw_select_circle(cr, self.hi_x - self.CIRCLE_HALF, self.hi_y - self.CIRCLE_HALF, ACTIVE_HI_COLOR, 8, 7, 8)
_draw_cursor_indicator(cr, self.cursor_x - 11, self.cursor_y - 11, 11)
class ColorBoxFilterEditor:
def __init__(self, editable_properties):
self.SAT_MAX = 0.5
self.widget = Gtk.VBox()
self.hue = [ep for ep in editable_properties if ep.name == "hue"][0]
self.saturation = [ep for ep in editable_properties if ep.name == "saturation"][0]
self.R = [ep for ep in editable_properties if ep.name == "R"][0]
self.G = [ep for ep in editable_properties if ep.name == "G"][0]
self.B = [ep for ep in editable_properties if ep.name == "B"][0]
self.color_box = ColorBox(self.color_box_values_changed)
self.color_box.set_cursor(self.hue.get_float_value(), self.saturation.get_float_value())
box_row = Gtk.HBox()
box_row.pack_start(Gtk.Label(), True, True, 0)
box_row.pack_start(self.color_box.widget, False, False, 0)
box_row.pack_start(Gtk.Label(), True, True, 0)
self.h_label = Gtk.Label()
self.s_label = Gtk.Label()
info_box = Gtk.HBox(True)
info_box.pack_start(self.h_label, False, False, 0)
info_box.pack_start(self.s_label, False, False, 0)
info_box.set_size_request(65, 20)
info_row = Gtk.HBox()
info_row.pack_start(Gtk.Label(), True, True, 0)
info_row.pack_start(info_box, False, False, 0)
info_row.pack_start(Gtk.Label(), True, True, 0)
self.widget.pack_start(box_row, False, False, 0)
self.widget.pack_start(info_row, False, False, 0)
self.widget.pack_start(Gtk.Label(), True, True, 0)
self._display_values(self.hue.get_float_value(), self.saturation.get_float_value())
def color_box_values_changed(self):
hue_val, sat_val = self.color_box.get_hue_saturation()
self.hue.write_property_value(str(hue_val))
self.saturation.write_property_value(str(sat_val))
self._display_values(hue_val, sat_val)
r, g, b = lutfilter.get_RGB_for_angle_saturation_and_value(hue_val * 360, sat_val * self.SAT_MAX, 0.5)
self.R.write_value("0=" + str(r))
self.G.write_value("0=" + str(g))
self.B.write_value("0=" + str(b))
def _display_values(self, hue, saturation):
sat_str = str(int(saturation * 100)) + "%"
hue_str = str(int(360 * hue)) + ColorGrader.DEGREE_CHAR + ' '
self.h_label.set_text(hue_str)
self.s_label.set_text(sat_str)
class ColorLGGFilterEditor:
def __init__(self, editable_properties):
self.widget = Gtk.VBox()
# Get MLT properties
self.lift_r = [ep for ep in editable_properties if ep.name == "lift_r"][0]
self.lift_g = [ep for ep in editable_properties if ep.name == "lift_g"][0]
self.lift_b = [ep for ep in editable_properties if ep.name == "lift_b"][0]
self.gamma_r = [ep for ep in editable_properties if ep.name == "gamma_r"][0]
self.gamma_g = [ep for ep in editable_properties if ep.name == "gamma_g"][0]
self.gamma_b = [ep for ep in editable_properties if ep.name == "gamma_b"][0]
self.gain_r = [ep for ep in editable_properties if ep.name == "gain_r"][0]
self.gain_g = [ep for ep in editable_properties if ep.name == "gain_g"][0]
self.gain_b = [ep for ep in editable_properties if ep.name == "gain_b"][0]
# Get Non-MLT properties
self.lift_hue = [ep for ep in editable_properties if ep.name == "lift_hue"][0]
self.lift_value = [ep for ep in editable_properties if ep.name == "lift_value"][0]
self.gamma_hue = [ep for ep in editable_properties if ep.name == "gamma_hue"][0]
self.gamma_value = [ep for ep in editable_properties if ep.name == "gamma_value"][0]
self.gain_hue = [ep for ep in editable_properties if ep.name == "gain_hue"][0]
self.gain_value = [ep for ep in editable_properties if ep.name == "gain_value"][0]
# Lift editor
self.lift_hue_selector = self.get_hue_selector(self.lift_hue_edited)
self.lift_hue_value_label = Gtk.Label()
self.lift_hue_row = self.get_hue_row(self.lift_hue_selector.widget, self.lift_hue_value_label)
self.lift_adjustment = self.lift_value.get_input_range_adjustment()
self.lift_adjustment.connect("value-changed", self.lift_value_changed)
self.lift_slider_row = self.get_slider_row(self.lift_adjustment)
self.update_lift_display(self.lift_hue.get_float_value(), self.lift_value.get_current_in_value())
# Gamma editor
self.gamma_hue_selector = self.get_hue_selector(self.gamma_hue_edited)
self.gamma_hue_value_label = Gtk.Label()
self.gamma_hue_row = self.get_hue_row(self.gamma_hue_selector.widget, self.gamma_hue_value_label)
self.gamma_adjustment = self.gamma_value.get_input_range_adjustment()
self.gamma_adjustment.connect("value-changed", self.gamma_value_changed)
self.gamma_slider_row = self.get_slider_row(self.gamma_adjustment)
self.update_gamma_display(self.gamma_hue.get_float_value(), self.gamma_value.get_current_in_value())
# Gain editor
self.gain_hue_selector = self.get_hue_selector(self.gain_hue_edited)
self.gain_hue_value_label = Gtk.Label()
self.gain_hue_row = self.get_hue_row(self.gain_hue_selector.widget, self.gain_hue_value_label)
self.gain_adjustment = self.gain_value.get_input_range_adjustment()
self.gain_adjustment.connect("value-changed", self.gain_value_changed)
self.gain_slider_row = self.get_slider_row(self.gain_adjustment)
self.update_gain_display(self.gain_hue.get_float_value(), self.gain_value.get_current_in_value())
# Pack
self.widget.pack_start(self.get_name_row("Lift"), True, True, 0)
self.widget.pack_start(self.lift_hue_row, True, True, 0)
self.widget.pack_start(self.lift_slider_row, True, True, 0)
self.widget.pack_start(guicomponents.EditorSeparator().widget, True, True, 0)
self.widget.pack_start(self.get_name_row("Gamma"), True, True, 0)
self.widget.pack_start(self.gamma_hue_row , True, True, 0)
self.widget.pack_start(self.gamma_slider_row , True, True, 0)
self.widget.pack_start(guicomponents.EditorSeparator().widget, True, True, 0)
self.widget.pack_start(self.get_name_row("Gain"), True, True, 0)
self.widget.pack_start(self.gain_hue_row , True, True, 0)
self.widget.pack_start(self.gain_slider_row , True, True, 0)
self.widget.pack_start(Gtk.Label(), True, True, 0)
# ---------------------------------------------- gui building
def get_hue_selector(self, callback):
color_box = ColorBox(callback, width=290, height=40)
color_box.draw_saturation_gradient = False
color_box.selection_cursor = SELECT_LINE
return color_box
def get_name_row(self, name):
name = _p(name)
name_label = Gtk.Label(label=name + ":")
hbox = Gtk.HBox(False, 4)
hbox.pack_start(name_label, False, False, 4)
hbox.pack_start(Gtk.Label(), True, True, 0)
return hbox
def get_hue_row(self, color_box, value_label):
hbox = Gtk.HBox(False, 4)
hbox.pack_start(color_box, False, False, 0)
hbox.pack_start(value_label, False, False, 4)
hbox.pack_start(Gtk.Label(), False, False, 0)
return hbox
def get_slider_row(self, adjustment):#, name):
hslider = Gtk.HScale()
hslider.set_adjustment(adjustment)
hslider.set_draw_value(False)
spin = Gtk.SpinButton()
spin.set_numeric(True)
spin.set_adjustment(adjustment)
hslider.set_digits(0)
spin.set_digits(0)
hbox = Gtk.HBox(False, 4)
#hbox.pack_start(name_label, False, False, 4)
hbox.pack_start(hslider, True, True, 0)
hbox.pack_start(spin, False, False, 4)
return hbox
# --------------------------------------- gui updating
def update_lift_display(self, hue, val):
self.lift_hue_selector.set_cursor(hue, 0.0)
self.set_hue_label_value(hue, self.lift_hue_value_label)
self.lift_adjustment.set_value(val)
def update_gamma_display(self, hue, val):
self.gamma_hue_selector.set_cursor(hue, 0.0)
self.set_hue_label_value(hue, self.gamma_hue_value_label)
self.gamma_adjustment.set_value(val)
def update_gain_display(self, hue, val):
self.gain_hue_selector.set_cursor(hue, 0.0)
self.set_hue_label_value(hue, self.gain_hue_value_label)
self.gain_adjustment.set_value(val)
def set_hue_label_value(self, hue, label):
hue_str = str(int(360 * hue)) + ColorGrader.DEGREE_CHAR + ' '
label.set_text(hue_str)
# ------------------------------ color box listeners
def lift_hue_edited(self):
hue, sat = self.lift_hue_selector.get_hue_saturation()
self.set_hue_label_value(hue, self.lift_hue_value_label)
self.update_lift_property_values()
def gamma_hue_edited(self):
hue, sat = self.gamma_hue_selector.get_hue_saturation()
self.set_hue_label_value(hue, self.gamma_hue_value_label)
self.update_gamma_property_values()
def gain_hue_edited(self):
hue, sat = self.gain_hue_selector.get_hue_saturation()
self.set_hue_label_value(hue, self.gain_hue_value_label)
self.update_gain_property_values()
# ----------------------------------- slider listeners
def lift_value_changed(self, adjustment):
self.update_lift_property_values()
def gamma_value_changed(self, adjustment):
self.update_gamma_property_values()
def gain_value_changed(self, adjustment):
self.update_gain_property_values()
# -------------------------------------- value writers
def update_lift_property_values(self):
hue, sat = self.lift_hue_selector.get_hue_saturation()
r, g, b = lutfilter.get_RGB_for_angle(hue * 360)
value = self.lift_adjustment.get_value() / 100.0
r = r * value
g = g * value
b = b * value
self.lift_hue.write_number_value(hue)
self.lift_value.write_number_value(value)
self.lift_r.write_value(r)
self.lift_g.write_value(g)
self.lift_b.write_value(b)
def update_gamma_property_values(self):
hue, sat = self.gamma_hue_selector.get_hue_saturation()
r, g, b = lutfilter.get_RGB_for_angle(hue * 360)
value = self.gamma_value.get_out_value(self.gamma_adjustment.get_value())
r = 1.0 + r * (value - 1.0)
g = 1.0 + g * (value - 1.0)
b = 1.0 + b * (value - 1.0)
self.gamma_hue.write_number_value(hue)
self.gamma_value.write_number_value(value)
self.gamma_r.write_value(r)
self.gamma_g.write_value(g)
self.gamma_b.write_value(b)
def update_gain_property_values(self):
hue, sat = self.gain_hue_selector.get_hue_saturation()
r, g, b = lutfilter.get_RGB_for_angle(hue * 360)
value = self.gain_value.get_out_value(self.gain_adjustment.get_value())
r = 1.0 + r * (value - 1.0)
g = 1.0 + g * (value - 1.0)
b = 1.0 + b * (value - 1.0)
self.gain_hue.write_number_value(hue)
self.gain_value.write_number_value(value)
self.gain_r.write_value(r)
self.gain_g.write_value(g)
self.gain_b.write_value(b)
class BoxEditor:
def __init__(self, pix_size):
self.value_size = 1.0 # Box editor works in 0-1 normalized space
self.pix_size = pix_size;
self.pix_per_val = self.value_size / pix_size
self.off_x = 0.5
self.off_y = 0.5
def get_box_val_point(self, x, y):
# calculate value
px = (x - self.off_x) * self.pix_per_val
py = (self.pix_size - (y - self.off_y)) * self.pix_per_val
# force range
if px < 0:
px = 0.0
if py < 0:
py = 0.0
if px >= self.value_size:
px = self.value_size
if py >= self.value_size:
py = self.value_size
return px, py
def get_box_panel_point(self, x, y, max_value):
px = x/max_value * self.pix_size + self.off_x
py = self.off_y + self.pix_size - (y/max_value * self.pix_size) # higher values are up
return (px, py)
def draw_box(self, cr, allocation):
x, y, w, h = allocation
if editorpersistance.prefs.theme == appconsts.LIGHT_THEME:
cr.set_source_rgb(*BOX_BG_COLOR )
cr.rectangle(0, 0, self.pix_size + 1, self.pix_size + 1)
cr.fill()
# value lines
cr.set_source_rgb(*BOX_LINE_COLOR)
step = self.pix_size / 8
cr.set_line_width(1.0)
for i in range(0, 9):
cr.move_to(0.5 + step * i, 0.5)
cr.line_to(step * i, self.pix_size + 0.5)
cr.stroke()
for i in range(0, 9):
cr.move_to(0.5, step * i + 0.5)
cr.line_to(self.pix_size + 0.5, step * i + 0.5)
cr.stroke()
class CatmullRomFilterEditor:
RGB = 0
R = 1
G = 2
B = 3
def __init__(self, editable_properties):
self.widget = Gtk.VBox()
# These properties hold the values that are writtenout to MLT to do the filtering
self.cr_filter = lutfilter.CatmullRomFilter(editable_properties)
default_curve = self.cr_filter.value_cr_curve
self.current_edit_curve = CatmullRomFilterEditor.RGB
# This is used to edit points of currently active curve
self.curve_editor = CurvesBoxEditor(256.0, default_curve, self)
# This is used to change currently active curve
self.channel_buttons = glassbuttons.GlassButtonsToggleGroup(32, 19, 2, 2, 5)
self.channel_buttons.add_button(cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "rgb_channel.png"), self.channel_changed)
self.channel_buttons.add_button(cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "red_channel.png"), self.channel_changed)
self.channel_buttons.add_button(cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "green_channel.png"), self.channel_changed)
self.channel_buttons.add_button(cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "blue_channel.png"), self.channel_changed)
self.channel_buttons.widget.set_pref_size(132, 28)
self.channel_buttons.set_pressed_button(0)
self.curve_buttons = glassbuttons.GlassButtonsGroup(32, 19, 2, 2, 5)
self.curve_buttons.add_button(cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "linear_curve.png"), self.do_curve_reset_pressed)
self.curve_buttons.add_button(cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "curve_s.png"), self.do_curve_reset_pressed)
self.curve_buttons.add_button(cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "curve_flipped_s.png"), self.do_curve_reset_pressed)
self.curve_buttons.widget.set_pref_size(97, 28)
button_hbox = Gtk.HBox()
button_hbox.pack_start(self.channel_buttons.widget, False, False, 0)
button_hbox.pack_start(guiutils.get_pad_label(4, 4), False, False, 0)
button_hbox.pack_start(self.curve_buttons.widget, False, False, 0)
buttons_row = guiutils.get_in_centering_alignment(button_hbox)
box_row = Gtk.HBox()
box_row.pack_start(Gtk.Label(), True, True, 0)
box_row.pack_start(self.curve_editor.widget, False, False, 0)
box_row.pack_start(Gtk.Label(), True, True, 0)
self.widget.pack_start(Gtk.Label(), True, True, 0)
self.widget.pack_start(box_row, False, False, 0)
self.widget.pack_start(guiutils.get_pad_label(12, 8), False, False, 0)
self.widget.pack_start(buttons_row, False, False, 0)
self.widget.pack_start(Gtk.Label(), True, True, 0)
def channel_changed(self):
channel = self.channel_buttons.pressed_button # indexes match
self.update_editors_to_channel(channel)
def update_editors_to_channel(self, channel):
# Channel values and button indexes match
if channel == CatmullRomFilterEditor.RGB:
self.current_edit_curve = CatmullRomFilterEditor.RGB
self.curve_editor.set_curve(self.cr_filter.value_cr_curve, CURVE_COLOR)
elif channel == CatmullRomFilterEditor.R:
self.current_edit_curve = CatmullRomFilterEditor.R
self.curve_editor.set_curve(self.cr_filter.r_cr_curve, R_CURVE_COLOR)
elif channel == CatmullRomFilterEditor.G:
self.current_edit_curve = CatmullRomFilterEditor.G
self.curve_editor.set_curve(self.cr_filter.g_cr_curve, G_CURVE_COLOR)
else:
self.current_edit_curve = CatmullRomFilterEditor.B
self.curve_editor.set_curve(self.cr_filter.b_cr_curve, B_CURVE_COLOR)
def do_curve_reset_pressed(self):
button_index = self.curve_buttons.pressed_button
channel = self.current_edit_curve
if button_index == 0: # Linear
new_points_str = "0/0;255/255"
elif button_index == 1: # Default add gamma
new_points_str = "0/0;64/48;192/208;255/255"
elif button_index == 2: # Default remove gamma
new_points_str = "0/0;64/80;192/176;255/255"
if channel == CatmullRomFilterEditor.RGB:
self.cr_filter.value_cr_curve.set_points_from_str(new_points_str)
elif channel == CatmullRomFilterEditor.R:
self.cr_filter.r_cr_curve.set_points_from_str(new_points_str)
elif channel== CatmullRomFilterEditor.G:
self.cr_filter.g_cr_curve.set_points_from_str(new_points_str)
else:
self.cr_filter.b_cr_curve.set_points_from_str(new_points_str)
self.write_points_to_current_curve(new_points_str)
self.update_editors_to_channel(channel)
def curve_edit_done(self):
points_str = self.curve_editor.curve.get_points_string()
self.write_points_to_current_curve(points_str)
def write_points_to_current_curve(self, points_str):
if self.current_edit_curve == CatmullRomFilterEditor.RGB:
self.cr_filter.value_points_prop.write_property_value(points_str)
elif self.current_edit_curve == CatmullRomFilterEditor.R:
self.cr_filter.r_points_prop.write_property_value(points_str)
elif self.current_edit_curve == CatmullRomFilterEditor.G:
self.cr_filter.g_points_prop.write_property_value(points_str)
else: # CatmullRomFilterEditor.B
self.cr_filter.b_points_prop.write_property_value(points_str)
self.cr_filter.update_table_property_values()
class CurvesBoxEditor(BoxEditor):
def __init__(self, pix_size, curve, edit_listener):
BoxEditor.__init__(self, pix_size)
self.curve = curve # lutfilter.CRCurve
global BOX_LINE_COLOR, CURVE_COLOR
self.curve_color = CURVE_COLOR
self.edit_listener = edit_listener # Needs to implement "curve_edit_done()"
self.widget = cairoarea.CairoDrawableArea2( self.pix_size + 2,
self.pix_size + 2,
self._draw)
self.widget.press_func = self._press_event
self.widget.motion_notify_func = self._motion_notify_event
self.widget.release_func = self._release_event
self.last_point = None
self.edit_on = False
if editorpersistance.prefs.theme != appconsts.LIGHT_THEME:
BOX_LINE_COLOR = (0.8, 0.8, 0.8)
CURVE_COLOR = (0.8, 0.8, 0.8)
self.curve_color = CURVE_COLOR
def set_curve(self, curve, curve_color):
self.curve = curve
self.curve_color = curve_color
self.widget.queue_draw()
def _press_event(self, event):
vx, vy = BoxEditor.get_box_val_point(self, event.x, event.y)
p = lutfilter.CurvePoint(int(round(vx * 255)), int(round(vy * 255)))
self.last_point = p
self.edit_on = True
self.curve.remove_range(self.last_point.x - 3, self.last_point.x + 3 )
self.curve.set_curve_point(p)
self.widget.queue_draw()
def _motion_notify_event(self, x, y, state):
if self.edit_on == False:
return
vx, vy = BoxEditor.get_box_val_point(self, x, y)
p = lutfilter.CurvePoint(int(round(vx * 255)), int(round(vy * 255)))
self.curve.remove_range(self.last_point.x, p.x)
self.curve.set_curve_point(p)
self.last_point = p
self.widget.queue_draw()
def _release_event(self, event):
if self.edit_on == False:
return
vx, vy = BoxEditor.get_box_val_point(self, event.x, event.y)
p = lutfilter.CurvePoint(int(round(vx * 255)),int(round(vy * 255)))
self.curve.remove_range(self.last_point.x, p.x)
self.curve.set_curve_point(p)
self.edit_on = False
self.edit_listener.curve_edit_done()
self.widget.queue_draw()
def _draw(self, event, cr, allocation):
# bg box
BoxEditor.draw_box(self, cr, allocation)
x, y, w, h = allocation
# curve
cr.set_source_rgb(*self.curve_color)# seg.setColor( CURVE_COLOR );
cr.set_line_width(1.5)
cp = self.curve.get_curve(True) #we get 256 values
px, py = BoxEditor.get_box_panel_point(self, 0, cp[0], 255)
cr.move_to(px, py)
for i in range(1, len(cp)): #int i = 0; i < cp.length - 1; i++ )
px, py = BoxEditor.get_box_panel_point(self, i, cp[i], 255.0)
cr.line_to(px, py)
cr.stroke()
cr.rectangle(1, 1, w - 3, h - 3)
cr.clip()
# edit points
for p in self.curve.points:
px, py = BoxEditor.get_box_panel_point(self, p.x, p.y, 255.0)
_draw_select_circle(cr, px, py, (1,1,1), 4, 2, 0, -4, -4)
class ColorGrader:
DEGREE_CHAR = '\u00B0'
def __init__(self, editable_properties):
# Initial active band
self.band = SHADOW
# HUE and SAT are both saved in range (0,1)
# HUE and SAT are both handled in editor using range (0,1)
# Saved and editor ranges are the same.
# ColorGradeBandCorrection objects handle ranges differently
# - saturation values 0-1 converted to range (-1, 1)
# - saturation value 0.5 is converted to 0 and means no correction applied
# - converted range(-1, 0) means negative correction applied
# - negative correction is interpreted as positive correction of complimentary color
# Editable properties
self.shadow_hue = [ep for ep in editable_properties if ep.name == "shadow_hue"][0]
self.shadow_saturation = [ep for ep in editable_properties if ep.name == "shadow_saturation"][0]
self.mid_hue = [ep for ep in editable_properties if ep.name == "mid_hue"][0]
self.mid_saturation = [ep for ep in editable_properties if ep.name == "mid_saturation"][0]
self.hi_hue = [ep for ep in editable_properties if ep.name == "hi_hue"][0]
self.hi_saturation = [ep for ep in editable_properties if ep.name == "hi_saturation"][0]
# Create filter and init values
self.filt = lutfilter.ColorGradeFilter(editable_properties)
self.filt.shadow_band.set_hue_and_saturation(self.shadow_hue.get_float_value(),
self.shadow_saturation.get_float_value())
self.filt.mid_band.set_hue_and_saturation(self.mid_hue.get_float_value(),
self.mid_saturation.get_float_value())
self.filt.hi_band.set_hue_and_saturation(self.hi_hue.get_float_value(),
self.hi_saturation.get_float_value())
self.filt.update_all_corrections()
self.filt.update_rgb_lookups()
self.filt.write_out_tables()
# Create GUI
self.color_box = ThreeBandColorBox(self.color_box_values_changed, self.band_changed, 340, 200)
self.color_box.set_cursor(self.shadow_hue.get_float_value(), self.shadow_saturation.get_float_value())
self.color_box.set_cursors(self.shadow_hue.get_float_value(), self.shadow_saturation.get_float_value(),
self.mid_hue.get_float_value(), self.mid_saturation.get_float_value(),
self.hi_hue.get_float_value(), self.hi_saturation.get_float_value())
box_row = Gtk.HBox()
box_row.pack_start(Gtk.Label(), True, True, 0)
box_row.pack_start(self.color_box.widget, False, False, 0)
box_row.pack_start(Gtk.Label(), True, True, 0)
shadow_icon = Gtk.Image.new_from_file(respaths.IMAGE_PATH + "shadow.png")
self.sh_label = Gtk.Label()
self.ss_label = Gtk.Label()
shadow_box = Gtk.HBox()
shadow_box.pack_start(shadow_icon, False, False, 0)
shadow_box.pack_start(guiutils.pad_label(3,5), False, False, 0)
shadow_box.pack_start(self.sh_label, False, False, 0)
shadow_box.pack_start(self.ss_label, False, False, 0)
shadow_box.set_size_request(95, 20)
midtone_icon = Gtk.Image.new_from_file(respaths.IMAGE_PATH + "midtones.png")
self.mh_label = Gtk.Label()
self.ms_label = Gtk.Label()
midtone_box = Gtk.HBox()
midtone_box.pack_start(midtone_icon, False, False, 0)
midtone_box.pack_start(guiutils.pad_label(3,5), False, False, 0)
midtone_box.pack_start(self.mh_label, False, False, 0)
midtone_box.pack_start(self.ms_label, False, False, 0)
midtone_box.set_size_request(95, 20)
highligh_icon = Gtk.Image.new_from_file(respaths.IMAGE_PATH + "highlights.png")
self.hh_label = Gtk.Label()
self.hs_label = Gtk.Label()
highlight_box = Gtk.HBox()
highlight_box.pack_start(highligh_icon, False, False, 0)
highlight_box.pack_start(guiutils.pad_label(3,5), False, False, 0)
highlight_box.pack_start(self.hh_label, False, False, 0)
highlight_box.pack_start(self.hs_label, False, False, 0)
highlight_box.set_size_request(95, 20)
self._display_values(SHADOW, self.shadow_hue.get_float_value(), self.shadow_saturation.get_float_value())
self._display_values(MID, self.mid_hue.get_float_value(), self.mid_saturation.get_float_value())
self._display_values(HI, self.hi_hue.get_float_value(), self.hi_saturation.get_float_value())
info_row = Gtk.HBox()
info_row.pack_start(Gtk.Label(), True, True, 0)
info_row.pack_start(shadow_box, False, False, 0)
info_row.pack_start(midtone_box, False, False, 0)
info_row.pack_start(highlight_box, False, False, 0)
info_row.pack_start(Gtk.Label(), True, True, 0)
self.widget = Gtk.VBox()
self.widget.pack_start(box_row, False, False, 0)
self.widget.pack_start(info_row, False, False, 0)
self.widget.pack_start(Gtk.Label(), True, True, 0)
def band_changed(self, band):
self.band = band
def color_box_values_changed(self):
hue, sat = self.color_box.get_hue_saturation()
if self.band == SHADOW:
self.shadow_hue.write_number_value(hue)
self.shadow_saturation.write_number_value(sat)
self.filt.shadow_band.set_hue_and_saturation(hue, sat)
self.filt.shadow_band.update_correction()
elif self.band == MID:
self.mid_hue.write_number_value(hue)
self.mid_saturation.write_number_value(sat)
self.filt.mid_band.set_hue_and_saturation(hue, sat)
self.filt.mid_band.update_correction()
else:
self.hi_hue.write_number_value(hue)
self.hi_saturation.write_number_value(sat)
self.filt.hi_band.set_hue_and_saturation(hue, sat)
self.filt.hi_band.update_correction()
self._display_values(self.band, hue, sat)
self.filt.update_rgb_lookups()
self.filt.write_out_tables()
def _display_values(self, band, hue, saturation):
sat_str = str(int(((saturation - 0.5) * 2.0) * 100)) + "%"
hue_str = str(int(360 * hue)) + ColorGrader.DEGREE_CHAR + ' '
if band == SHADOW:
self.sh_label.set_text(hue_str)
self.ss_label.set_text(sat_str)
elif band == MID:
self.mh_label.set_text(hue_str)
self.ms_label.set_text(sat_str)
else:
self.hh_label.set_text(hue_str)
self.hs_label.set_text(sat_str)
"""
# NON_ MLT PROPERTY SLIDER DEMO CODE
def hue_changed(self, ep, value):
ep.write_property_value(str(value))
self.update_properties()
def saturation_changed(self, ep, value):
ep.write_property_value(str(value))
self.update_properties()
def value_changed(self, ep, value):
ep.write_property_value(str(value))
self.update_properties()
"""
"""
class AbstractColorWheel:
def __init__(self, edit_listener):
self.widget = cairoarea.CairoDrawableArea2( 260,
260,
self._draw)
self.widget.press_func = self._press_event
self.widget.motion_notify_func = self._motion_notify_event
self.widget.release_func = self._release_event
self.X_PAD = 3
self.Y_PAD = 3
self.CENTER_X = 130
self.CENTER_Y = 130
self.MAX_DIST = 123
self.twelwe_p = (self.CENTER_X , self.CENTER_Y - self.MAX_DIST)
self.CIRCLE_HALF = 6
self.cursor_x = self.CENTER_X
self.cursor_y = self.CENTER_Y
self.WHEEL_IMG = GdkPixbuf.Pixbuf.new_from_file(respaths.IMAGE_PATH + "color_wheel.png")
self.edit_listener = edit_listener
self.angle = 0.0
self.distance = 0.0
def _press_event(self, event):
self.cursor_x, self.cursor_y = self._get_legal_point(event.x, event.y)
self._save_point()
self.widget.queue_draw()
def _motion_notify_event(self, x, y, state):
self.cursor_x, self.cursor_y = self._get_legal_point(x, y)
self._save_point()
self.widget.queue_draw()
def _release_event(self, event):
self.cursor_x, self.cursor_y = self._get_legal_point(event.x, event.y)
self._save_point()
self.edit_listener()
self.widget.queue_draw()
def _get_legal_point(self, x, y):
vec = viewgeom.get_vec_for_points((self.CENTER_X, self.CENTER_Y), (x, y))
dist = vec.get_length()
if dist < self.MAX_DIST:
return (x, y)
new_vec = vec.get_multiplied_vec(self.MAX_DIST / dist )
return new_vec.end_point
def get_angle(self, p):
angle = viewgeom.get_angle_in_deg(self.twelwe_p, (self.CENTER_X, self.CENTER_Y), p)
clockwise = viewgeom.points_clockwise(self.twelwe_p, (self.CENTER_X, self.CENTER_Y), p)
if clockwise:
angle = 360.0 - angle;
# Color circle starts from 11 o'clock
angle = angle - 30.0
if angle < 0.0:
angle = angle + 360.0
return angle
def get_distance(self, p):
vec = viewgeom.get_vec_for_points((self.CENTER_X, self.CENTER_Y), p)
dist = vec.get_length()
return dist/self.MAX_DIST
def _save_point(self):
print "_save_point not implemented"
pass
def get_angle_and_distance(self):
if self.band == SHADOW:
x = self.shadow_x
y = self.shadow_y
elif self.band == MID:
x = self.mid_x
y = self.mid_y
else:
x = self.hi_x
y = self.hi_y
p = (x, y)
angle = self._get_angle(p)
distance = self._get_distance(p)
return (angle, distance)
def _draw(self, event, cr, allocation):
x, y, w, h = allocation
# Draw bg
cr.set_source_rgb(*(gui.bg_color_tuple))
cr.rectangle(0, 0, w, h)
cr.fill()
cr.set_source_pixbuf(self.WHEEL_IMG, self.X_PAD, self.Y_PAD)
cr.paint()
class SimpleColorWheel(AbstractColorWheel):
def __init__(self, edit_listener):
AbstractColorWheel.__init__(self, edit_listener)
self.value_x = self.cursor_x
self.value_y = self.cursor_y
def _save_point(self):
self.value_x = self.cursor_x
self.value_y = self.cursor_y
def get_angle_and_distance(self):
p = (self.value_x, self.value_y)
angle = self.get_angle(p)
distance = self.get_distance(p)
return (angle, distance)
def _draw(self, event, cr, allocation):
AbstractColorWheel._draw(self, event, cr, allocation)
_draw_select_circle(cr, self.cursor_x - self.CIRCLE_HALF, self.cursor_y - self.CIRCLE_HALF, (1,1,1), ACTIVE_RING_COLOR)
class SMHColorWheel(AbstractColorWheel):
def __init__(self, edit_listener):
AbstractColorWheel.__init__(self, edit_listener)
self.band = SHADOW
self.shadow_x = self.cursor_x
self.shadow_y = self.cursor_y
self.mid_x = self.cursor_x
self.mid_y = self.cursor_y
self.hi_x = self.cursor_x
self.hi_y = self.cursor_y
def set_band(self, band):
self.band = band
if self.band == SHADOW:
self.cursor_x = self.shadow_x
self.cursor_y = self.shadow_y
elif self.band == MID:
self.cursor_x = self.mid_x
self.cursor_y = self.mid_y
else:
self.cursor_x = self.hi_x
self.cursor_y = self.hi_y
def _save_point(self):
if self.band == SHADOW:
self.shadow_x = self.cursor_x
self.shadow_y = self.cursor_y
elif self.band == MID:
self.mid_x = self.cursor_x
self.mid_y = self.cursor_y
else:
self.hi_x = self.cursor_x
self.hi_y = self.cursor_y
def get_angle_and_distance(self):
if self.band == SHADOW:
x = self.shadow_x
y = self.shadow_y
elif self.band == MID:
x = self.mid_x
y = self.mid_y
else:
x = self.hi_x
y = self.hi_y
p = (x, y)
angle = self.get_angle(p)
distance = self.get_distance(p)
return (angle, distance)
def _draw(self, event, cr, allocation):
AbstractColorWheel._draw(self, event, cr, allocation)
if self.band == SHADOW:
band_color = ACTIVE_SHADOW_COLOR
elif self.band == MID:
band_color = ACTIVE_MID_COLOR
else:
band_color = ACTIVE_HI_COLOR
_draw_select_circle(cr, self.cursor_x - self.CIRCLE_HALF, self.cursor_y - self.CIRCLE_HALF, band_color, ACTIVE_RING_COLOR)
"""
class ColorBandSelector:
def __init__(self):
self.band = SHADOW
self.widget = cairoarea.CairoDrawableArea2( 42,
18,
self._draw)
self.widget.press_func = self._press_event
self.SHADOW_X = 0
self.MID_X = 15
self.HI_X = 30
self.band_change_listener = None # monkey patched in at creation site
def _press_event(self, event):
x = event.x
y = event.y
if self._circle_hit(self.SHADOW_X, x, y):
self.band_change_listener(SHADOW)
elif self._circle_hit(self.MID_X, x, y):
self.band_change_listener(MID)
elif self._circle_hit(self.HI_X, x, y):
self.band_change_listener(HI)
def _circle_hit(self, band_x, x, y):
if x >= band_x and x < band_x + 12:
if y > 0 and y < 12:
return True
return False
def _draw(self, event, cr, allocation):
"""
Callback for repaint from CairoDrawableArea.
We get cairo context and allocation.
"""
x, y, w, h = allocation
ring_color = (0.0, 0.0, 0.0)
_draw_select_circle(cr, self.SHADOW_X, 0, (0.1, 0.1, 0.1), ring_color)
_draw_select_circle(cr, self.MID_X, 0, (0.5, 0.5, 0.5), ring_color)
_draw_select_circle(cr, self.HI_X, 0, (1.0, 1.0, 1.0), ring_color)
self._draw_active_indicator(cr)
def _draw_active_indicator(self, cr):
y = 14.5
HALF = 4.5
HEIGHT = 2
if self.band == SHADOW:
x = self.SHADOW_X + 1.5
elif self.band == MID:
x = self.MID_X + 1.5
else:
x = self.HI_X + 1.5
cr.set_source_rgb(0, 0, 0)
cr.move_to(x, y)
cr.line_to(x + 2 * HALF, y)
cr.line_to(x + 2 * HALF, y + HEIGHT)
cr.line_to(x, y + HEIGHT)
cr.close_path()
cr.fill()
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/glassbuttons.py 0000664 0000000 0000000 00000052253 13610327166 0026214 0 ustar 00root root 0000000 0000000 """
Flowblade Movie Editor is a nonlinear video editor.
Copyright 2012 Janne Liljeblad.
This file is part of Flowblade Movie Editor .
Flowblade Movie Editor 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.
Flowblade Movie Editor is distributed in the hope that 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 Flowblade Movie Editor. If not, see .
"""
import cairo
import math
import appconsts
import cairoarea
import editorpersistance
import gui
import guiutils # Aug-2019 - SvdB - BB
import respaths
BUTTONS_GRAD_STOPS = [ (1, 1, 1, 1, 0.2),
(0.8, 1, 1, 1, 0),
(0.51, 1, 1, 1, 0),
(0.50, 1, 1, 1, 0.25),
(0, 1, 1, 1, 0.4)]
BUTTONS_PRESSED_GRAD_STOPS = [(1, 0.7, 0.7, 0.7, 1),
(0, 0.5, 0.5, 0.5, 1)]
LINE_GRAD_STOPS = [ (1, 0.66, 0.66, 0.66, 1),
(0.95, 0.7, 0.7, 0.7, 1),
(0.65, 0.3, 0.3, 0.3, 1),
(0, 0.64, 0.64, 0.64, 1)]
BUTTON_NOT_SENSITIVE_GRAD_STOPS = [(1, 0.9, 0.9, 0.9, 0.7),
(0, 0.9, 0.9, 0.9, 0.7)]
CORNER_DIVIDER = 5
# Aug-2019 - SvdB - BB
MB_BUTTONS_WIDTH = [317,634]
MB_BUTTONS_HEIGHT = [30,60]
MB_BUTTON_HEIGHT = [22,44]
MB_BUTTON_WIDTH = [30,60]
MB_BUTTON_Y = 4
MB_BUTTON_IMAGE_Y = 6
GMIC_BUTTONS_WIDTH = 250
M_PI = math.pi
NO_HIT = -1
# Focus groups are used to test if one widget in the group of buttons widgets has keyboard focus
DEFAULT_FOCUS_GROUP = "default_focus_group"
focus_groups = {DEFAULT_FOCUS_GROUP:[]}
class AbstractGlassButtons:
def __init__(self, button_width, button_height, button_y, widget_width, widget_height):
# Create widget and connect listeners
self.widget = cairoarea.CairoDrawableArea2( widget_width,
widget_height,
self._draw)
self.widget.press_func = self._press_event
self.widget.motion_notify_func = self._motion_notify_event
self.widget.release_func = self._release_event
self.pressed_callback_funcs = None # set later
self.released_callback_funcs = None # set later
self.pressed_button = -1
self.degrees = M_PI / 180.0
self.button_width = button_width
self.button_height = button_height
self.button_y = button_y
self.button_x = 0 # set when first allocation known by extending class
self.icons = []
self.image_x = []
self.image_y = []
self.sensitive = []
if editorpersistance.prefs.buttons_style == editorpersistance.GLASS_STYLE:
self.glass_style = True
else:
self.glass_style = False
self.no_decorations = False
# Dark theme comes with flat buttons
self.dark_theme = False
if editorpersistance.prefs.theme != appconsts.LIGHT_THEME:
self.glass_style = False
self.dark_theme = True
self.draw_button_gradients = True # old code artifact, remove (set False at object creation site to kill all gradients)
def _set_button_draw_consts(self, x, y, width, height):
aspect = 1.0
corner_radius = height / CORNER_DIVIDER
radius = corner_radius / aspect
self._draw_consts = (x, y, width, height, aspect, corner_radius, radius)
def set_sensitive(self, value):
self.sensitive = []
for i in self.icons:
self.sensitive.append(value)
def _round_rect_path(self, cr):
x, y, width, height, aspect, corner_radius, radius = self._draw_consts
degrees = self.degrees
cr.new_sub_path()
cr.arc (x + width - radius, y + radius, radius, -90 * degrees, 0 * degrees)
cr.arc (x + width - radius, y + height - radius, radius, 0 * degrees, 90 * degrees)
cr.arc (x + radius, y + height - radius, radius, 90 * degrees, 180 * degrees)
cr.arc (x + radius, y + radius, radius, 180 * degrees, 270 * degrees)
cr.close_path ()
def _press_event(self, event):
print("_press_event not impl")
def _motion_notify_event(self, x, y, state):
print("_motion_notify_event not impl")
def _release_event(self, event):
print("_release_event not impl")
def _draw(self, event, cr, allocation):
print("_draw not impl")
def _get_hit_code(self, x, y):
button_x = self.button_x
for i in range(0, len(self.icons)):
if ((x >= button_x) and (x <= button_x + self.button_width)
and (y >= self.button_y) and (y <= self.button_y + self.button_height)):
if self.sensitive[i] == True:
return i
button_x += self.button_width
return NO_HIT
def _draw_buttons(self, cr, w, h):
# Width of buttons group
buttons_width = self.button_width * len(self.icons)
if self.no_decorations == True:
x = self.button_x
for i in range(0, len(self.icons)):
icon = self.icons[i]
cr.set_source_surface(icon, x + self.image_x[i], self.image_y[i])
cr.paint()
x += self.button_width
return
# Line width for all strokes
cr.set_line_width(1.0)
# bg
self._set_button_draw_consts(self.button_x + 0.5, self.button_y + 0.5, buttons_width, self.button_height + 1.0)
self._round_rect_path(cr)
r, g, b, a = gui.get_bg_color()
if self.draw_button_gradients:
if self.glass_style == True:
cr.set_source_rgb(0.75, 0.75, 0.75)
cr.fill_preserve()
else:
grad = cairo.LinearGradient (self.button_x, self.button_y, self.button_x, self.button_y + self.button_height)
if self.dark_theme == False:
grad.add_color_stop_rgba(1, r - 0.1, g - 0.1, b - 0.1, 1)
grad.add_color_stop_rgba(0, r + 0.1, g + 0.1, b + 0.1, 1)
else:
grad.add_color_stop_rgba(1, r + 0.04, g + 0.04, b + 0.04, 1)
grad.add_color_stop_rgba(0, r + 0.07, g + 0.07, b + 0.07, 1)
cr.set_source(grad)
cr.fill_preserve()
# Pressed button gradient
if self.pressed_button > -1:
if self.draw_button_gradients:
grad = cairo.LinearGradient (self.button_x, self.button_y, self.button_x, self.button_y + self.button_height)
if self.glass_style == True:
for stop in BUTTONS_PRESSED_GRAD_STOPS:
grad.add_color_stop_rgba(*stop)
else:
grad = cairo.LinearGradient (self.button_x, self.button_y, self.button_x, self.button_y + self.button_height)
grad.add_color_stop_rgba(1, r - 0.3, g - 0.3, b - 0.3, 1)
grad.add_color_stop_rgba(0, r - 0.1, g - 0.1, b - 0.1, 1)
else:
grad = cairo.LinearGradient (self.button_x, self.button_y, self.button_x, self.button_y + self.button_height)
grad.add_color_stop_rgba(1, r - 0.3, g - 0.3, b - 0.3, 1)
grad.add_color_stop_rgba(0, r - 0.3, g - 0.3, b - 0.3, 1)
cr.save()
cr.set_source(grad)
cr.clip()
cr.rectangle(self.button_x + self.pressed_button * self.button_width, self.button_y, self.button_width, self.button_height)
cr.fill()
cr.restore()
# Icons and sensitive gradient
grad = cairo.LinearGradient (self.button_x, self.button_y, self.button_x, self.button_y + self.button_height)
for stop in BUTTON_NOT_SENSITIVE_GRAD_STOPS:
grad.add_color_stop_rgba(*stop)
x = self.button_x
for i in range(0, len(self.icons)):
icon = self.icons[i]
cr.set_source_surface(icon, x + self.image_x[i], self.image_y[i])
cr.paint()
if self.sensitive[i] == False:
cr.save()
self._round_rect_path(cr)
cr.set_source(grad)
cr.clip()
cr.rectangle(x, self.button_y, self.button_width, self.button_height)
cr.fill()
cr.restore()
x += self.button_width
if self.glass_style == True and self.draw_button_gradients:
# Glass gradient
self._round_rect_path(cr)
grad = cairo.LinearGradient (self.button_x, self.button_y, self.button_x, self.button_y + self.button_height)
for stop in BUTTONS_GRAD_STOPS:
grad.add_color_stop_rgba(*stop)
cr.set_source(grad)
cr.fill()
else:
pass
if self.dark_theme != True:
# Round line
grad = cairo.LinearGradient (self.button_x, self.button_y, self.button_x, self.button_y + self.button_height)
for stop in LINE_GRAD_STOPS:
grad.add_color_stop_rgba(*stop)
cr.set_source(grad)
self._set_button_draw_consts(self.button_x + 0.5, self.button_y + 0.5, buttons_width, self.button_height)
self._round_rect_path(cr)
cr.stroke()
if self.dark_theme == True:
cr.set_source_rgb(0,0,0)
# Vert lines
x = self.button_x
for i in range(0, len(self.icons)):
if (i > 0) and (i < len(self.icons)):
cr.move_to(x + 0.5, self.button_y)
cr.line_to(x + 0.5, self.button_y + self.button_height)
cr.stroke()
x += self.button_width
class PlayerButtons(AbstractGlassButtons):
def __init__(self):
# Aug-2019 - SvdB - BB - Multiple changes - size_ind, size_adj, get_cairo_image
size_ind = 0
size_adj = 1
prefs = editorpersistance.prefs
if prefs.double_track_hights:
size_ind = 1
size_adj = 2
AbstractGlassButtons.__init__(self, MB_BUTTON_WIDTH[size_ind], MB_BUTTON_HEIGHT[size_ind], MB_BUTTON_Y, MB_BUTTONS_WIDTH[size_ind], MB_BUTTONS_HEIGHT[size_ind])
play_pause_icon = guiutils.get_cairo_image("play_pause_s")
play_icon = guiutils.get_cairo_image("play_2_s")
stop_icon = guiutils.get_cairo_image("stop_s")
next_icon = guiutils.get_cairo_image("next_frame_s")
prev_icon = guiutils.get_cairo_image("prev_frame_s")
mark_in_icon = guiutils.get_cairo_image("mark_in_s")
mark_out_icon = guiutils.get_cairo_image("mark_out_s")
marks_clear_icon = guiutils.get_cairo_image("marks_clear_s")
to_mark_in_icon = guiutils.get_cairo_image("to_mark_in_s")
to_mark_out_icon = guiutils.get_cairo_image("to_mark_out_s")
# Jul-2016 - SvdB - For play/pause button
if (editorpersistance.prefs.play_pause == True):
self.icons = [prev_icon, next_icon, play_pause_icon,
mark_in_icon, mark_out_icon,
marks_clear_icon, to_mark_in_icon, to_mark_out_icon]
self.image_x = [5*size_adj, 7*size_adj, 5*size_adj, 3*size_adj, 11*size_adj, 2*size_adj, 7*size_adj, 6*size_adj]
else:
self.icons = [prev_icon, next_icon, play_icon, stop_icon,
mark_in_icon, mark_out_icon,
marks_clear_icon, to_mark_in_icon, to_mark_out_icon]
self.image_x = [5*size_adj, 7*size_adj, 10*size_adj, 10*size_adj, 3*size_adj, 11*size_adj, 2*size_adj, 7*size_adj, 6*size_adj]
for i in range(0, len(self.icons)):
self.image_y.append(MB_BUTTON_IMAGE_Y)
self.pressed_callback_funcs = None # set using set_callbacks()
self.set_sensitive(True)
focus_groups[DEFAULT_FOCUS_GROUP].append(self.widget)
def set_trim_sensitive_pattern(self):
# Jul-2016 - SvdB - For play/pause button
if (editorpersistance.prefs.play_pause == True):
self.sensitive = [True, True, True, False, False, False, False, False]
else:
self.sensitive = [True, True, True, True, False, False, False, False, False]
self.widget.queue_draw()
def set_normal_sensitive_pattern(self):
self.set_sensitive(True)
self.widget.queue_draw()
# ------------------------------------------------------------- mouse events
def _press_event(self, event):
"""
Mouse button callback
"""
self.pressed_button = self._get_hit_code(event.x, event.y)
if self.pressed_button >= 0 and self.pressed_button < len(self.icons):
callback_func = self.pressed_callback_funcs[self.pressed_button] # index is set to match at editorwindow.py where callback func list is created
callback_func()
self.widget.queue_draw()
def _motion_notify_event(self, x, y, state):
"""
Mouse move callback
"""
button_under = self._get_hit_code(x, y)
if self.pressed_button != button_under: # pressed button is released
self.pressed_button = NO_HIT
self.widget.queue_draw()
def _release_event(self, event):
"""
Mouse release callback
"""
self.pressed_button = -1
self.widget.queue_draw()
def set_callbacks(self, pressed_callback_funcs):
self.pressed_callback_funcs = pressed_callback_funcs
# ---------------------------------------------------------------- painting
def _draw(self, event, cr, allocation):
x, y, w, h = allocation
self.allocation = allocation
mid_x = w / 2
buttons_width = self.button_width * len(self.icons)
# Jul-2016 - SvdB - No changes made here, but because of the calculation of button_x the row of buttons is slightly moved right if play/pause
# is enabled. This could be solved by setting self.button_x = 1, if wished.
self.button_x = mid_x - (buttons_width / 2)
self._draw_buttons(cr, w, h)
class GmicButtons(AbstractGlassButtons):
def __init__(self):
AbstractGlassButtons.__init__(self, MB_BUTTON_WIDTH[0], MB_BUTTON_HEIGHT[0], MB_BUTTON_Y, GMIC_BUTTONS_WIDTH, MB_BUTTONS_HEIGHT[0])
IMG_PATH = respaths.IMAGE_PATH
next_icon = cairo.ImageSurface.create_from_png(IMG_PATH + "next_frame_s.png")
prev_icon = cairo.ImageSurface.create_from_png(IMG_PATH + "prev_frame_s.png")
mark_in_icon = cairo.ImageSurface.create_from_png(IMG_PATH + "mark_in_s.png")
mark_out_icon = cairo.ImageSurface.create_from_png(IMG_PATH + "mark_out_s.png")
marks_clear_icon = cairo.ImageSurface.create_from_png(IMG_PATH + "marks_clear_s.png")
to_mark_in_icon = cairo.ImageSurface.create_from_png(IMG_PATH + "to_mark_in_s.png")
to_mark_out_icon = cairo.ImageSurface.create_from_png(IMG_PATH + "to_mark_out_s.png")
self.icons = [prev_icon, next_icon, mark_in_icon, mark_out_icon,
marks_clear_icon, to_mark_in_icon, to_mark_out_icon]
self.image_x = [8, 10, 6, 14, 5, 10, 9]
for i in range(0, len(self.icons)):
self.image_y.append(MB_BUTTON_IMAGE_Y)
self.pressed_callback_funcs = None # set using set_callbacks()
self.set_sensitive(True)
focus_groups[DEFAULT_FOCUS_GROUP].append(self.widget)
def set_normal_sensitive_pattern(self):
self.set_sensitive(True)
self.widget.queue_draw()
# ------------------------------------------------------------- mouse events
def _press_event(self, event):
"""
Mouse button callback
"""
self.pressed_button = self._get_hit_code(event.x, event.y)
if self.pressed_button >= 0 and self.pressed_button < len(self.icons):
callback_func = self.pressed_callback_funcs[self.pressed_button] # index is set to match at editorwindow.py where callback func list is created
callback_func()
self.widget.queue_draw()
def _motion_notify_event(self, x, y, state):
"""
Mouse move callback
"""
button_under = self._get_hit_code(x, y)
if self.pressed_button != button_under: # pressed button is released
self.pressed_button = NO_HIT
self.widget.queue_draw()
def _release_event(self, event):
"""
Mouse release callback
"""
self.pressed_button = -1
self.widget.queue_draw()
def set_callbacks(self, pressed_callback_funcs):
self.pressed_callback_funcs = pressed_callback_funcs
# ---------------------------------------------------------------- painting
def _draw(self, event, cr, allocation):
x, y, w, h = allocation
self.allocation = allocation
mid_x = w / 2
buttons_width = self.button_width * len(self.icons)
self.button_x = mid_x - (buttons_width / 2)
self._draw_buttons(cr, w, h)
class GlassButtonsGroup(AbstractGlassButtons):
def __init__(self, button_width, button_height, button_y, image_x_default, image_y_default, focus_group=DEFAULT_FOCUS_GROUP):
AbstractGlassButtons.__init__(self, button_width, button_height, button_y, button_width, button_height)
self.released_callback_funcs = []
self.image_x_default = image_x_default
self.image_y_default = image_y_default
focus_groups[focus_group].append(self.widget)
def add_button(self, pix_buf, release_callback):
self.icons.append(pix_buf)
self.released_callback_funcs.append(release_callback)
self.image_x.append(self.image_x_default)
self.image_y.append(self.image_y_default)
self.sensitive.append(True)
self.widget.set_pref_size(len(self.icons) * self.button_width + 2, self.button_height + 2)
def _draw(self, event, cr, allocation):
x, y, w, h = allocation
self.allocation = allocation
self.button_x = 0
self._draw_buttons(cr, w, h)
def _press_event(self, event):
self.pressed_button = self._get_hit_code(event.x, event.y)
self.widget.queue_draw()
def _motion_notify_event(self, x, y, state):
button_under = self._get_hit_code(x, y)
if self.pressed_button != button_under: # pressed button is released if mouse moves from over it
if self.pressed_button > 0 and self.pressed_button < len(self.icons):
release_func = self.released_callback_funcs[self.pressed_button]
release_func()
self.pressed_button = NO_HIT
self.widget.queue_draw()
def _release_event(self, event):
if self.pressed_button >= 0 and self.pressed_button < len(self.icons):
release_func = self.released_callback_funcs[self.pressed_button]
release_func()
self.pressed_button = -1
self.widget.queue_draw()
class GlassButtonsToggleGroup(GlassButtonsGroup):
def set_pressed_button(self, pressed_button_index, fire_clicked_cb=False):
self.pressed_button = pressed_button_index
if fire_clicked_cb == True:
self._fire_pressed_button()
self.widget.queue_draw()
def _fire_pressed_button(self):
release_func = self.released_callback_funcs[self.pressed_button]
release_func()
def _press_event(self, event):
new_pressed_button = self._get_hit_code(event.x, event.y)
if new_pressed_button == NO_HIT:
return
if new_pressed_button != self.pressed_button:
self.pressed_button = new_pressed_button
self._fire_pressed_button()
self.widget.queue_draw()
def _motion_notify_event(self, x, y, state):
pass
def _release_event(self, event):
pass
class TooltipRunner:
def __init__(self, glassbuttons, tooltips):
self.glassbuttons = glassbuttons
self.tooltips = tooltips
self.glassbuttons.widget.set_has_tooltip(True)
self.glassbuttons.widget.connect("query-tooltip", self.tooltip_query)
self.glassbuttons.tooltip_runner = self
self.last_hit_code = NO_HIT
def tooltip_query(self, widget, x, y, keyboard_tooltip, tooltip):
hit_code = self.glassbuttons._get_hit_code(x, y)
if hit_code == NO_HIT:
return False
# This is needed to get better position for tooltips when tooltips have significantly different amount of text displayed
if hit_code != self.last_hit_code:
self.last_hit_code = hit_code
self.glassbuttons.widget.trigger_tooltip_query()
return False
tooltip.set_markup(self.tooltips[hit_code])
return True
def focus_group_has_focus(focus_group):
group = focus_groups[focus_group]
for widget in group:
if widget.has_focus():
return True
return False
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/gui.py 0000664 0000000 0000000 00000023663 13610327166 0024253 0 ustar 00root root 0000000 0000000 """
Flowblade Movie Editor is a nonlinear video editor.
Copyright 2012 Janne Liljeblad.
This file is part of Flowblade Movie Editor .
Flowblade Movie Editor 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.
Flowblade Movie Editor is distributed in the hope that 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 Flowblade Movie Editor. If not, see .
"""
"""
Module holds references to GUI widgets and offers some helper fuctions used in GUI creation.
"""
from gi.repository import Gtk, Gdk
import pickle
import appconsts
import atomicfile
import editorpersistance
import respaths
import userfolders
import utils
# Editor window
editor_window = None
# Menu
editmenu = None
# Project data lists and related views.
media_list_view = None
bin_list_view = None
bin_panel = None
sequence_list_view = None
effect_stack_list_view = None
middle_notebook = None # This is now the only notebook, maybe update name sometime
project_info_vbox = None
effect_select_list_view = None
effect_select_combo_box = None
render_out_folder = None
# Media tab
media_view_filter_selector = None
# Monitor
pos_bar = None
# Timeline
tline_display = None
tline_scale = None
tline_canvas = None
tline_scroll = None
tline_info = None # Shows save icon
tline_column = None
tline_left_corner = None
big_tc = None
monitor_widget = None
monitor_switch = None
# indexes match editmode values in editorstate.py
notebook_buttons = None
sequence_editor_b = None
# Theme colors
# Theme colors are given as 4 RGB tuples and string, ((LIGHT_BG), (DARK_BG), (SELECTED_BG), (DARK_SELECTED_BG), name)
_UBUNTU_COLORS = ((0.949020, 0.945098, 0.941176), (0.172, 0.172, 0.172), (0.941, 0.466, 0.274, 0.9), (0.941, 0.466, 0.274, 0.9), "Ubuntu")
_GNOME_COLORS = ((0.929412, 0.929412, 0.929412), (0.172, 0.172, 0.172), (0.28627451, 0.560784314, 0.843137255), (0.192, 0.361, 0.608), "Gnome")
_MINT_COLORS = ((0.839215686, 0.839215686, 0.839215686), (0.172, 0.172, 0.172), (0.556862745, 0.678431373, 0.439215686), (0.556862745, 0.678431373, 0.439215686), "Linux Mint")
_ARC_COLORS = ((0.960784, 0.964706, 0.968627), (0.266667, 0.282353, 0.321569), (0.321568627, 0.580392157, 0.88627451), (0.321568627, 0.580392157, 0.88627451), "Arc (theme)")
_FLOWBLADE_COLORS = ((0.960784, 0.964706, 0.968627), (0.266667, 0.282353, 0.321569), (0.1, 0.31, 0.58), (0.1, 0.31, 0.58), "Flowblade Theme")
_THEME_COLORS = (_UBUNTU_COLORS, _GNOME_COLORS, _MINT_COLORS, _ARC_COLORS, _FLOWBLADE_COLORS)
_CURRENT_THEME_COLORS_FILE = "currentcolors.data" # Used to communicate theme colors to tools like gmic.py running on separate process
_selected_bg_color = None
_bg_color = None
_button_colors = None
def capture_references(new_editor_window):
"""
Create shorter names for some of the frequently used GUI objects.
"""
global editor_window, media_list_view, bin_list_view, sequence_list_view, pos_bar, \
tline_display, tline_scale, tline_canvas, tline_scroll, tline_v_scroll, tline_info, \
tline_column, play_b, \
effect_select_list_view, effect_select_combo_box, project_info_vbox, middle_notebook, big_tc, editmenu, notebook_buttons, tline_left_corner, \
monitor_widget, bin_panel, monitor_switch
editor_window = new_editor_window
media_list_view = editor_window.media_list_view
bin_list_view = editor_window.bin_list_view
bin_panel = editor_window.bins_panel
sequence_list_view = editor_window.sequence_list_view
middle_notebook = editor_window.notebook
effect_select_list_view = editor_window.effect_select_list_view
effect_select_combo_box = editor_window.effect_select_combo_box
pos_bar = editor_window.pos_bar
monitor_widget = editor_window.monitor_widget
monitor_switch = editor_window.monitor_switch
tline_display = editor_window.tline_display
tline_scale = editor_window.tline_scale
tline_canvas = editor_window.tline_canvas
tline_scroll = editor_window.tline_scroller
tline_info = editor_window.tline_info
tline_column = editor_window.tline_column
tline_left_corner = editor_window.left_corner
big_tc = editor_window.big_TC
editmenu = editor_window.uimanager.get_widget('/MenuBar/EditMenu')
def enable_save():
editor_window.uimanager.get_widget("/MenuBar/FileMenu/Save").set_sensitive(True)
# returns Gdk.RGBA color
def get_bg_color():
return _bg_color
# returns Gdk.RGBA color
def get_selected_bg_color():
return _selected_bg_color
# returns Gdk.RGBA color
#def get_buttons_color():
# return _button_colors
def set_theme_colors():
# Find out if theme color discovery works and set selected bg color apppropiately when
# this is first called.
global _selected_bg_color, _bg_color, _button_colors
fallback_theme_colors = editorpersistance.prefs.theme_fallback_colors
theme_colors = _THEME_COLORS[fallback_theme_colors]
# Try to detect selected color and set from fallback if fails
style = editor_window.bin_list_view.get_style_context()
sel_bg_color = style.get_background_color(Gtk.StateFlags.SELECTED)
r, g, b, a = unpack_gdk_color(sel_bg_color)
if r == 0.0 and g == 0.0 and b == 0.0:
print("Selected color NOT detected")
if editorpersistance.prefs.theme == appconsts.LIGHT_THEME:
c = theme_colors[2]
else:
c = theme_colors[3]
_selected_bg_color = Gdk.RGBA(*c)
else:
print("Selected color detected")
_selected_bg_color = sel_bg_color
# Try to detect bg color and set frow fallback if fails
style = editor_window.window.get_style_context()
bg_color = style.get_background_color(Gtk.StateFlags.NORMAL)
if editorpersistance.prefs.theme == appconsts.FLOWBLADE_THEME:
bg_color = Gdk.RGBA(red=(30.0/255.0), green=(35.0/255.0), blue=(51.0/255.0), alpha=1.0)
r, g, b, a = unpack_gdk_color(bg_color)
if r == 0.0 and g == 0.0 and b == 0.0:
print("BG color NOT detected")
if editorpersistance.prefs.theme == appconsts.LIGHT_THEME:
c = theme_colors[0]
else:
c = theme_colors[1]
_bg_color = Gdk.RGBA(*c)
_button_colors = Gdk.RGBA(*c)
else:
print("BG color detected")
_bg_color = bg_color
_button_colors = bg_color
if editorpersistance.prefs.theme == appconsts.FLOWBLADE_THEME:
theme_colors = _THEME_COLORS[4]
c = theme_colors[3]
_selected_bg_color = Gdk.RGBA(*c)
# Adwaita and some others show big area of black without this, does not bother Ambient on Ubuntu
editor_window.tline_pane.override_background_color(Gtk.StateFlags.NORMAL, get_bg_color())
editor_window.media_panel.override_background_color(Gtk.StateFlags.NORMAL, get_bg_color())
editor_window.mm_paned.override_background_color(Gtk.StateFlags.NORMAL, get_bg_color())
def apply_flowblade_theme_fixes():
fblade_bg_color = Gdk.RGBA(red=(30.0/255.0), green=(35.0/255.0), blue=(51.0/255.0), alpha=1.0)
fblade_bg_color_darker = Gdk.RGBA(red=(16.0/255.0), green=(19.0/255.0), blue=(30.0/255.0), alpha=1.0)
test_color = Gdk.RGBA(1, 0, 0, alpha=1.0)
for widget in editor_window.fblade_theme_fix_panels:
widget.override_background_color(Gtk.StateFlags.NORMAL, fblade_bg_color)
for widget in editor_window.fblade_theme_fix_panels_darker:
widget.override_background_color(Gtk.StateFlags.NORMAL, fblade_bg_color_darker)
def unpack_gdk_color(gdk_color):
return (gdk_color.red, gdk_color.green, gdk_color.blue, gdk_color.alpha)
def save_current_colors():
# Used to communicate theme colors to tools like gmic.py running on separate process
colors = (unpack_gdk_color(_selected_bg_color), unpack_gdk_color(_bg_color), unpack_gdk_color(_button_colors))
save_file_path = _colors_data_path()
with atomicfile.AtomicFileWriter(save_file_path, "wb") as afw:
write_file = afw.get_file()
pickle.dump(colors, write_file)
def load_current_colors():
load_path = _colors_data_path()
colors = utils.unpickle(load_path)
sel, bg, button = colors
global _selected_bg_color, _bg_color, _button_colors
_selected_bg_color = Gdk.RGBA(*sel)
_bg_color = Gdk.RGBA(*bg)
_button_colors = Gdk.RGBA(*button)
def _colors_data_path():
return userfolders.get_cache_dir() + _CURRENT_THEME_COLORS_FILE
def _print_widget(widget): # debug
path_str = widget.get_path().to_string()
path_str = path_str.replace("GtkWindow:dir-ltr.background","")
path_str = path_str.replace("dir-ltr","")
path_str = path_str.replace("vertical","")
path_str = path_str.replace("horizontal","")
path_str = path_str.replace("[1/2]","")
path_str = path_str.replace("GtkVBox:. GtkVPaned:[2/2]. GtkHBox:. GtkHPaned:. GtkVBox:. GtkNotebook:[1/1]","notebook:")
print(path_str)
def apply_gtk_css():
gtk_version = "%s.%s.%s" % (Gtk.get_major_version(), Gtk.get_minor_version(), Gtk.get_micro_version())
if Gtk.get_major_version() == 3 and Gtk.get_minor_version() >= 22:
print("Gtk version is " + gtk_version + ", Flowblade theme is available.")
else:
print("Gtk version is " + gtk_version + ", Flowblade theme only available for Gtk >= 3.22")
editorpersistance.prefs.theme = appconsts.LIGHT_THEME
editorpersistance.save()
return False
provider = Gtk.CssProvider.new()
display = Gdk.Display.get_default()
screen = display.get_default_screen()
Gtk.StyleContext.add_provider_for_screen (screen, provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
provider.load_from_path(respaths.ROOT_PATH + "/res/css/gtk-flowblade-dark.css")
return True
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/guicomponents.py 0000664 0000000 0000000 00000363306 13610327166 0026362 0 ustar 00root root 0000000 0000000 """
Flowblade Movie Editor is a nonlinear video editor.
Copyright 2012 Janne Liljeblad.
This file is part of Flowblade Movie Editor .
Flowblade Movie Editor 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.
Flowblade Movie Editor is distributed in the hope that 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 Flowblade Movie Editor. If not, see .
"""
"""
Module contains classes and build methods to create GUI objects.
"""
import cairo
import copy
import math
import time
import gi
gi.require_version('PangoCairo', '1.0')
from gi.repository import GObject
from gi.repository import GdkPixbuf
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import Pango
from gi.repository import PangoCairo
from gi.repository import GLib
import appconsts
import cairoarea
import dnd
import editorpersistance
import editorstate
from editorstate import current_sequence
from editorstate import current_bin
from editorstate import PROJECT
from editorstate import PLAYER
import gui
import guiutils
import mltfilters
import mltprofiles
import mlttransitions
import monitorwidget
import respaths
import shortcuts
import snapping
import toolsintegration
import translations
import utils
SEPARATOR_HEIGHT = 5
SEPARATOR_WIDTH = 250
MONITOR_COMBO_WIDTH = 32
MONITOR_COMBO_HEIGHT = 12
MEDIA_OBJECT_WIDGET_WIDTH = 120
MEDIA_OBJECT_WIDGET_HEIGHT = 105
CLIP_EDITOR_LEFT_WIDTH = 200
TC_COLOR = (0.7, 0.7, 0.7)
BIG_TC_GRAD_STOPS = [ (1, 1, 1, 1, 0.2),
(0.8, 1, 1, 1, 0),
(0.51, 1, 1, 1, 0),
(0.50, 1, 1, 1, 0.25),
(0, 1, 1, 1, 0.4)]
BIG_TC_FRAME_GRAD_STOPS = [ (1, 0.7, 0.7, 0.7, 1),
(0.95, 0.7, 0.7, 0.7, 1),
(0.75, 0.1, 0.1, 0.1, 1),
(0, 0.14, 0.14, 0.14, 1)]
M_PI = math.pi
has_proxy_icon = None
is_proxy_icon = None
graphics_icon = None
imgseq_icon = None
audio_icon = None
pattern_icon = None
profile_warning_icon = None
# GTK3 requires these to be created outside of callback
markers_menu = Gtk.Menu.new()
tracks_menu = Gtk.Menu.new()
monitor_menu = Gtk.Menu.new()
trim_view_menu = Gtk.Menu.new()
tools_menu = Gtk.Menu.new()
file_filter_menu = Gtk.Menu()
column_count_menu = Gtk.Menu()
clip_popup_menu = Gtk.Menu()
tracks_pop_menu = Gtk.Menu()
transition_clip_menu = Gtk.Menu()
blank_clip_menu = Gtk.Menu()
audio_clip_menu = Gtk.Menu()
compositor_popup_menu = Gtk.Menu()
media_file_popup_menu = Gtk.Menu()
filter_stack_menu_popup_menu = Gtk.Menu()
media_linker_popup_menu = Gtk.Menu()
log_event_popup_menu = Gtk.Menu()
levels_menu = Gtk.Menu()
clip_effects_hamburger_menu = Gtk.Menu()
bin_popup_menu = Gtk.Menu()
# ------------------------------------------------- item lists
class ImageTextTextListView(Gtk.VBox):
"""
GUI component displaying list with columns: img, text, text
Middle column expands.
"""
def __init__(self):
GObject.GObject.__init__(self)
# Datamodel: icon, text, text
self.storemodel = Gtk.ListStore(GdkPixbuf.Pixbuf, str, str)
# Scroll container
self.scroll = Gtk.ScrolledWindow()
self.scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
self.scroll.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
# View
self.treeview = Gtk.TreeView(self.storemodel)
self.treeview.set_property("rules_hint", True)
self.treeview.set_headers_visible(False)
tree_sel = self.treeview.get_selection()
tree_sel.set_mode(Gtk.SelectionMode.SINGLE)
# Column views
self.icon_col = Gtk.TreeViewColumn("Icon")
self.text_col_1 = Gtk.TreeViewColumn("text1")
self.text_col_2 = Gtk.TreeViewColumn("text2")
# Cell renderers
self.icon_rend = Gtk.CellRendererPixbuf()
self.icon_rend.props.xpad = 6
self.text_rend_1 = Gtk.CellRendererText()
self.text_rend_1.set_property("ellipsize", Pango.EllipsizeMode.END)
self.text_rend_2 = Gtk.CellRendererText()
self.text_rend_2.set_property("yalign", 0.0)
# Build column views
self.icon_col.set_expand(False)
self.icon_col.set_spacing(5)
self.icon_col.pack_start(self.icon_rend, False)
self.icon_col.add_attribute(self.icon_rend, 'pixbuf', 0)
self.text_col_1.set_expand(True)
self.text_col_1.set_spacing(5)
self.text_col_1.set_sizing(Gtk.TreeViewColumnSizing.GROW_ONLY)
self.text_col_1.set_min_width(150)
self.text_col_1.pack_start(self.text_rend_1, True)
self.text_col_1.add_attribute(self.text_rend_1, "text", 1)
self.text_col_2.set_expand(False)
self.text_col_2.pack_start(self.text_rend_2, True)
self.text_col_2.add_attribute(self.text_rend_2, "text", 2)
# Add column views to view
self.treeview.append_column(self.icon_col)
self.treeview.append_column(self.text_col_1)
self.treeview.append_column(self.text_col_2)
# Build widget graph and display
self.scroll.add(self.treeview)
self.pack_start(self.scroll, True, True, 0)
self.scroll.show_all()
def get_selected_rows_list(self):
model, rows = self.treeview.get_selection().get_selected_rows()
return rows
# ------------------------------------------------- item lists
class BinTreeView(Gtk.VBox):
"""
GUI component displaying list with columns: img, text, text
Middle column expands.
"""
def __init__(self, bin_selection_cb, bin_name_edit_cb, bins_popup_cb):
GObject.GObject.__init__(self)
self.bins_popup_cb = bins_popup_cb
# Datamodel: icon, text, text (folder, name, item count)
self.storemodel = Gtk.ListStore(GdkPixbuf.Pixbuf, str, str)
# Scroll container
self.scroll = Gtk.ScrolledWindow()
self.scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
self.scroll.set_shadow_type(Gtk.ShadowType.NONE)
# TreeView
self.treeview = Gtk.TreeView(self.storemodel)
self.treeview.connect('button-press-event', self._button_press_event)
self.treeview.set_property("rules_hint", True)
self.treeview.set_headers_visible(False)
tree_sel = self.treeview.get_selection()
tree_sel.set_mode(Gtk.SelectionMode.SINGLE)
tree_sel.connect("changed", bin_selection_cb)
# Cell renderers
self.icon_rend = Gtk.CellRendererPixbuf()
self.text_rend_1 = Gtk.CellRendererText()
self.text_rend_1.set_property("editable", True)
self.text_rend_1.connect("edited",
bin_name_edit_cb,
(self.storemodel, 1))
self.text_rend_1.set_property("ellipsize", Pango.EllipsizeMode.END)
self.text_rend_2 = Gtk.CellRendererText()
# Column views
self.bin_col = Gtk.TreeViewColumn("")
self.bin_col.set_expand(True)
self.bin_col.pack_start(self.icon_rend, False)
self.bin_col.add_attribute(self.icon_rend, 'pixbuf', 0)
self.bin_col.pack_start(self.text_rend_1, True)
self.bin_col.add_attribute(self.text_rend_1, 'text', 1)
self.item_count_col = Gtk.TreeViewColumn("", self.text_rend_2, text=2)
self.item_count_col.set_expand(False)
# Add column views to view
self.treeview.append_column(self.bin_col)
self.treeview.append_column(self.item_count_col)
# Build widget graph and display
self.scroll.add(self.treeview)
self.pack_start(self.scroll, True, True, 0)
self.scroll.show_all()
self.scroll.connect('button-press-event', self._button_press_event)
def get_selected_rows_list(self):
model, rows = self.treeview.get_selection().get_selected_rows()
return rows
def fill_data_model(self):
self.storemodel.clear()
for media_bin in PROJECT().bins:
try:
#Gtk.Image.new_from_stock(Gtk.STOCK_OPEN, Gtk.IconSize.MENU)
pixbuf = GdkPixbuf.Pixbuf.new_from_file(respaths.IMAGE_PATH + "bin_5.png")
row_data = [pixbuf,
media_bin.name,
str(len(media_bin.file_ids))]
self.storemodel.append(row_data)
except GObject.GError as exc:
print("can't load icon", exc)
self.scroll.queue_draw()
def _button_press_event(self, widget, event):
if event.button == 3:
self.bins_popup_cb(event)
# ------------------------------------------------- item lists
class ImageTextImageListView(Gtk.VBox):
"""
GUI component displaying list with columns: img, text, img
Middle column expands.
"""
def __init__(self):
GObject.GObject.__init__(self)
# Datamodel: icon, text, icon
self.storemodel = Gtk.ListStore(GdkPixbuf.Pixbuf, str, GdkPixbuf.Pixbuf)
# Scroll container
self.scroll = Gtk.ScrolledWindow()
self.scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
self.scroll.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
# View
self.treeview = Gtk.TreeView(self.storemodel)
self.treeview.set_property("rules_hint", True)
self.treeview.set_headers_visible(False)
tree_sel = self.treeview.get_selection()
tree_sel.set_mode(Gtk.SelectionMode.SINGLE)
# Column views
self.icon_col_1 = Gtk.TreeViewColumn("icon1")
self.text_col_1 = Gtk.TreeViewColumn("text1")
self.icon_col_2 = Gtk.TreeViewColumn("icon2")
# Cell renderers
self.icon_rend_1 = Gtk.CellRendererPixbuf()
self.icon_rend_1.props.xpad = 6
self.text_rend_1 = Gtk.CellRendererText()
self.text_rend_1.set_property("ellipsize", Pango.EllipsizeMode.END)
self.icon_rend_2 = Gtk.CellRendererPixbuf()
self.icon_rend_2.props.xpad = 6
# Build column views
self.icon_col_1.set_expand(False)
self.icon_col_1.set_spacing(5)
self.icon_col_1.pack_start(self.icon_rend_1, False)
self.icon_col_1.add_attribute(self.icon_rend_1, 'pixbuf', 0)
self.text_col_1.set_expand(True)
self.text_col_1.set_spacing(5)
self.text_col_1.set_sizing(Gtk.TreeViewColumnSizing.GROW_ONLY)
self.text_col_1.set_min_width(150)
self.text_col_1.pack_start(self.text_rend_1, True)
self.text_col_1.add_attribute(self.text_rend_1, "text", 1)
self.icon_col_2.set_expand(False)
self.icon_col_2.set_spacing(5)
self.icon_col_2.pack_start(self.icon_rend_2, False)
self.icon_col_2.add_attribute(self.icon_rend_2, 'pixbuf', 2)
# Add column views to view
self.treeview.append_column(self.icon_col_1)
self.treeview.append_column(self.text_col_1)
self.treeview.append_column(self.icon_col_2)
self.scroll.add(self.treeview)
self.pack_start(self.scroll, True, True, 0)
self.scroll.show_all()
def get_selected_rows_list(self):
model, rows = self.treeview.get_selection().get_selected_rows()
return rows
class SequenceListView(ImageTextTextListView):
"""
GUI component displaying list of sequences in project
"""
def __init__(self, seq_name_edited_cb, sequence_popup_cb, double_click_cb):
ImageTextTextListView.__init__(self)
self.sequence_popup_cb = sequence_popup_cb
self.treeview.connect('button-press-event', self._button_press_event)
self.scroll.set_shadow_type(Gtk.ShadowType.NONE)
self.double_click_cb = double_click_cb
self.double_click_counter = 0 # We get 2 events for double click, we use this to only do one callback
# Icon path
self.icon_path = respaths.IMAGE_PATH + "sequence.png"
# Set sequence name editable and connect 'edited' signal
self.text_rend_1.set_property("editable", True)
self.text_rend_1.connect("edited",
seq_name_edited_cb,
(self.storemodel, 1))
self.scroll.connect('button-press-event', self._button_press_event)
def fill_data_model(self):
"""
Creates displayed data.
Displays icon, sequence name and sequence length
"""
self.storemodel.clear()
for seq in PROJECT().sequences:
icon = GdkPixbuf.Pixbuf.new_from_file(self.icon_path)
active = ""
if seq == current_sequence():
active = _("active") + " "
row_data = [icon,
seq.name,
active]
self.storemodel.append(row_data)
self.scroll.queue_draw()
def _button_press_event(self, widget, event):
if event.button == 3:
self.sequence_popup_cb(event)
# Double click handled separately
if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS:
self.double_click_counter += 1
if self.double_click_counter == 2:
self.double_click_counter = 0
self.double_click_cb()
class MediaListView(ImageTextTextListView):
"""
GUI component displaying list of media files.
"""
def __init__(self, row_activated_cb, file_name_edited_cb):
ImageTextTextListView.__init__(self)
# Connect double-click listener and allow multiple selection
self.treeview.connect("row-activated",
row_activated_cb)
tree_sel = self.treeview.get_selection()
tree_sel.set_mode(Gtk.SelectionMode.MULTIPLE)
self.text_rend_1.set_property("editable", True)
self.text_rend_1.set_property("font-desc", Pango.FontDescription("sans bold 9"))
self.text_rend_1.connect("edited",
file_name_edited_cb,
(self.storemodel, 1))
self.text_rend_2.set_property("font-desc", Pango.FontDescription("sans 8"))
self.text_rend_2.set_property("yalign", 0.5)
def fill_data_model(self):
"""
Creates displayed data.
Displays thumbnail icon, file name and length
"""
self.storemodel.clear()
for file_id in current_bin().file_ids:
media_file = PROJECT().media_files[file_id]
row_data = [media_file.icon,
media_file.name,
utils.clip_length_string(media_file.length)]
self.storemodel.append(row_data)
self.scroll.queue_draw()
class BinListView(ImageTextTextListView):
"""
GUI component displaying list of media files.
"""
def __init__(self, bin_selection_cb, bin_name_edit_cb):
ImageTextTextListView.__init__(self)
self.text_col_1.set_min_width(10)
# Connect selection 'changed' signal
tree_sel = self.treeview.get_selection()
tree_sel.connect("changed", bin_selection_cb)
# Set bin name editable and connect 'edited' signal
self.text_rend_1.set_property("editable", True)
self.text_rend_1.connect("edited",
bin_name_edit_cb,
(self.storemodel, 1))
def fill_data_model(self):
self.storemodel.clear()
for media_bin in PROJECT().bins:
try:
pixbuf = GdkPixbuf.Pixbuf.new_from_file(respaths.IMAGE_PATH + "bin_5.png")
row_data = [pixbuf,
media_bin.name,
str(len(media_bin.file_ids))]
self.storemodel.append(row_data)
self.scroll.queue_draw()
except GObject.GError as exc:
print("can't load icon", exc)
class FilterListView(ImageTextImageListView):
"""
GUI component displaying list of available filters.
"""
def __init__(self, selection_cb=None):
ImageTextImageListView.__init__(self)
# Connect selection 'changed' signal
if not(selection_cb == None):
tree_sel = self.treeview.get_selection()
tree_sel.connect("changed", selection_cb)
def fill_data_model(self, filter_group):
self.storemodel.clear()
for i in range(0, len(filter_group)):
f = filter_group[i]
row_data = [f.get_icon(),
translations.get_filter_name(f.name),
None] # None is historical on/off icon thingy, not used anymore
self.storemodel.append(row_data)
self.scroll.queue_draw()
class FilterSwitchListView(Gtk.VBox):
"""
GUI component displaying list of filters applied to a clip.
"""
def __init__(self, selection_cb, toggle_cb, row_deleted, row_inserted):
GObject.GObject.__init__(self)
# Datamodel: icon, text, icon
self.storemodel = Gtk.ListStore(GdkPixbuf.Pixbuf, str, bool)
self.storemodel.connect("row-deleted", row_deleted)
self.storemodel.connect("row-inserted", row_inserted)
# Scroll container
self.scroll = Gtk.ScrolledWindow()
self.scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
self.scroll.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
# View
self.treeview = Gtk.TreeView(self.storemodel)
self.treeview.set_property("rules_hint", True)
self.treeview.set_headers_visible(False)
self.treeview.set_reorderable(True)
tree_sel = self.treeview.get_selection()
tree_sel.set_mode(Gtk.SelectionMode.SINGLE)
# Column views
self.icon_col_1 = Gtk.TreeViewColumn("icon1")
self.text_col_1 = Gtk.TreeViewColumn("text1")
self.check_col_1 = Gtk.TreeViewColumn("switch")
# Cell renderers
self.icon_rend_1 = Gtk.CellRendererPixbuf()
self.icon_rend_1.props.xpad = 6
self.text_rend_1 = Gtk.CellRendererText()
self.text_rend_1.set_property("ellipsize", Pango.EllipsizeMode.END)
self.toggle_rend = Gtk.CellRendererToggle()
self.toggle_rend.set_property('activatable', True)
self.toggle_rend.connect( 'toggled', self.toggled)
# Build column views
self.icon_col_1.set_expand(False)
self.icon_col_1.set_spacing(5)
self.icon_col_1.pack_start(self.icon_rend_1, False)
self.icon_col_1.add_attribute(self.icon_rend_1, 'pixbuf', 0)
self.text_col_1.set_expand(True)
self.text_col_1.set_spacing(5)
self.text_col_1.set_sizing(Gtk.TreeViewColumnSizing.GROW_ONLY)
self.text_col_1.set_min_width(150)
self.text_col_1.pack_start(self.text_rend_1, True)
self.text_col_1.add_attribute(self.text_rend_1, "text", 1)
self.check_col_1.set_expand(False)
self.check_col_1.set_spacing(5)
self.check_col_1.pack_start(self.toggle_rend, False)
self.check_col_1.add_attribute(self.toggle_rend, "active", 2)
# Add column views to view
self.treeview.append_column(self.icon_col_1)
self.treeview.append_column(self.text_col_1)
self.treeview.append_column(self.check_col_1)
# Build widget graph and display
self.scroll.add(self.treeview)
self.pack_start(self.scroll, True, True, 0)
self.scroll.show_all()
# Connect selection 'changed' signal
if not(selection_cb == None):
tree_sel = self.treeview.get_selection()
tree_sel.connect("changed", selection_cb)
self.toggle_callback = toggle_cb
def get_selected_rows_list(self):
model, rows = self.treeview.get_selection().get_selected_rows()
return rows
def fill_data_model(self, filter_group, filter_objects):
"""
Creates displayed data.
Displays thumbnail icon, file name and length
filter_group is array of mltfilter.FilterInfo objects.
filter_obejcts is array of mltfilter.FilterObject objects
"""
self.storemodel.clear()
for i in range(0, len(filter_group)):
f = filter_group[i]
row_data = [f.get_icon(),
translations.get_filter_name(f.name),
filter_objects[i].active]
self.storemodel.append(row_data)
self.scroll.queue_draw()
def toggled(self, cell, path):
self.toggle_callback(int(path))
class TextListView(Gtk.VBox):
"""
GUI component displaying list with single column text column.
"""
def __init__(self, width, column_name=None):
GObject.GObject.__init__(self)
# Datamodel: icon, text, text
self.storemodel = Gtk.ListStore(str)
# Scroll container
self.scroll = Gtk.ScrolledWindow()
self.scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
self.scroll.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
# View
self.treeview = Gtk.TreeView(self.storemodel)
self.treeview.set_property("rules_hint", True)
if column_name == None:
self.treeview.set_headers_visible(False)
column_name = "text1"
self.treeview.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE)
# Cell renderers
self.text_rend_1 = Gtk.CellRendererText()
self.text_rend_1.set_property("ellipsize", Pango.EllipsizeMode.END)
# Build column views
self.text_col_1 = Gtk.TreeViewColumn(column_name)
self.text_col_1.set_expand(True)
self.text_col_1.set_spacing(5)
self.text_col_1.set_sizing(Gtk.TreeViewColumnSizing.GROW_ONLY)
self.text_col_1.set_min_width(width)
self.text_col_1.pack_start(self.text_rend_1, False)
self.text_col_1.add_attribute(self.text_rend_1, "text", 0)
# Add column views to view
self.treeview.append_column(self.text_col_1)
# Build widget graph and display
self.scroll.add(self.treeview)
self.pack_start(self.scroll, True, True, 0)
self.scroll.show_all()
def get_selected_rows_list(self):
model, rows = self.treeview.get_selection().get_selected_rows()
return rows
def get_selected_indexes_list(self):
rows = self.get_selected_rows_list()
indexes = []
for row in rows:
indexes.append(max(row))
return indexes
class ProfileListView(TextListView):
"""
GUI component displaying list with columns: img, text, text
Middle column expands.
"""
def __init__(self, column_name=None):
TextListView.__init__(self, 100, column_name)
def fill_data_model(self, profiles):
self.storemodel.clear()
default_profile = mltprofiles.get_default_profile()
for profile in profiles:
row_data = [profile[0]]
if default_profile == profile[1]:
row_data = [row_data[0] + " <" + _("default") + ">"]
self.storemodel.append(row_data)
self.scroll.queue_draw()
class AutoSavesListView(TextListView):
def __init__(self, column_name=None):
TextListView.__init__(self, 300, None)
self.treeview.get_selection().set_mode(Gtk.SelectionMode.SINGLE)
def fill_data_model(self, autosaves):
self.storemodel.clear()
for autosave_object in autosaves:
since_time_str = utils.get_time_str_for_sec_float(autosave_object.age)
row_data = [_("Autosave created ") + since_time_str + _(" ago.")]
self.storemodel.append(row_data)
self.treeview.set_cursor("0")
self.scroll.queue_draw()
# -------------------------------------------- clip info
class ClipInfoPanel(Gtk.HBox):
def __init__(self):
GObject.GObject.__init__(self)
if editorstate.screen_size_small_height() == True:
font_desc = "sans bold 8"
else:
font_desc = "sans bold 9"
self.name_label = guiutils.bold_label(_("Clip:"))
self.name_value = Gtk.Label()
self.name_value.set_ellipsize(Pango.EllipsizeMode.END)
self.name_label.set_sensitive(False)
self.name_value.set_sensitive(False)
self.name_value.modify_font(Pango.FontDescription(font_desc))
self.name_label.modify_font(Pango.FontDescription(font_desc))
self.track = guiutils.bold_label(_("Track:"))
self.track_value = Gtk.Label()
self.track.set_sensitive(False)
self.track_value.set_sensitive(False)
self.track.modify_font(Pango.FontDescription(font_desc))
self.track_value.modify_font(Pango.FontDescription(font_desc))
info_row_1 = Gtk.HBox()
info_row_1.pack_start(self.name_label, False, True, 0)
info_row_1.pack_start(self.name_value, True, True, 0)
info_row_2 = Gtk.HBox()
info_row_2.pack_start(self.track, False, False, 0)
info_row_2.pack_start(self.track_value, True, True, 0)
self.pack_start(info_row_1, False, False, 0)
self.pack_start(guiutils.pad_label(24,12), False, False, 0)
self.pack_start(info_row_2, False, False, 0)
def display_clip_info(self, clip, track, index):
self.name_label.set_text(_("Clip: "))
self.name_value.set_text("" + clip.name + "")
self.track.set_text(_("Track: "))
self.track_value.set_text("" + track.get_name() + "")
self._set_use_mark_up()
def set_no_clip_info(self):
self.name_label.set_text("")
self.name_value.set_text("")
self.track.set_text("")
self.track_value.set_text("")
self._set_use_mark_up()
def _set_use_mark_up(self):
self.name_label.set_use_markup(True)
self.track.set_use_markup(True)
self.name_value.set_use_markup(True)
self.track_value.set_use_markup(True)
def set_enabled(self, value):
pass
# -------------------------------------------- compositor info
class CompositorInfoPanel(Gtk.HBox):
def __init__(self):
GObject.GObject.__init__(self)
self.set_homogeneous(False)
if editorstate.screen_size_small_height() == True:
font_desc = "sans bold 8"
else:
font_desc = "sans bold 9"
self.source_track = Gtk.Label()
self.source_track_value = Gtk.Label()
self.source_track.modify_font(Pango.FontDescription(font_desc))
self.source_track_value.modify_font(Pango.FontDescription(font_desc))
self.source_track.set_sensitive(False)
self.source_track_value.set_sensitive(False)
self.destination_track = Gtk.Label()
self.destination_track_value = Gtk.Label()
self.destination_track.modify_font(Pango.FontDescription(font_desc))
self.destination_track_value.modify_font(Pango.FontDescription(font_desc))
self.destination_track.set_sensitive(False)
self.destination_track_value.set_sensitive(False)
self.length = Gtk.Label()
self.length_value = Gtk.Label()
self.length.modify_font(Pango.FontDescription(font_desc))
self.length_value.modify_font(Pango.FontDescription(font_desc))
self.length.set_sensitive(False)
self.length_value.set_sensitive(False)
info_row_2 = Gtk.HBox()
info_row_2.pack_start(self.source_track, False, True, 0)
info_row_2.pack_start(self.source_track_value, False, False, 0)
info_row_2.pack_start(Gtk.Label(), True, True, 0)
info_row_3 = Gtk.HBox()
info_row_3.pack_start(self.destination_track, False, False, 0)
info_row_3.pack_start(self.destination_track_value, False, False, 0)
info_row_3.pack_start(Gtk.Label(), True, True, 0)
info_row_5 = Gtk.HBox()
info_row_5.pack_start(self.length, False, False, 0)
info_row_5.pack_start(self.length_value, False, False, 0)
info_row_5.pack_start(Gtk.Label(), True, True, 0)
PAD_HEIGHT = 2
self.pack_start(info_row_2, False, False, 0)
self.pack_start(info_row_3, False, False, 0)
self.pack_start(info_row_5, False, False, 0)
self.set_spacing(4)
self.set_no_compositor_info()
self.set_enabled(False)
def display_compositor_info(self, compositor):
self.source_track.set_text(_("Source:") + " ")
self.destination_track.set_text(_("Destination:") + " ")
self.length.set_text(_("Length:") + " ")
src_track = utils.get_track_name(current_sequence().tracks[compositor.transition.b_track],current_sequence())
self.source_track_value.set_text("" + src_track + "")
dest_track = utils.get_track_name(current_sequence().tracks[compositor.transition.a_track], current_sequence())
self.destination_track_value.set_text("" + dest_track + "")
length = utils.get_tc_string(compositor.clip_out - compositor.clip_in)
self.length_value.set_text("" + length + "")
self._set_use_mark_up()
def set_no_compositor_info(self):
self.source_track.set_text("")
self.source_track_value.set_text("")
self.destination_track.set_text("")
self.destination_track_value.set_text("")
self.length.set_text("")
self.length_value.set_text("")
self._set_use_mark_up()
def _set_use_mark_up(self):
self.source_track.set_use_markup(True)
self.destination_track.set_use_markup(True)
self.length.set_use_markup(True)
self.length_value.set_use_markup(True)
self.destination_track_value.set_use_markup(True)
self.source_track_value.set_use_markup(True)
def set_enabled(self, value):
pass # Seek and destroy callsites
# -------------------------------------------- compositor info
class BinInfoPanel(Gtk.HBox):
def __init__(self):
GObject.GObject.__init__(self)
self.set_homogeneous(False)
if editorstate.screen_size_small_height() == True:
font_desc = "sans bold 8"
else:
font_desc = "sans bold 9"
self.bin_name = Gtk.Label()
self.bin_name.modify_font(Pango.FontDescription(font_desc))
self.bin_name.set_sensitive(False)
self.items = Gtk.Label()
self.items_value = Gtk.Label()
self.items.modify_font(Pango.FontDescription(font_desc))
self.items_value.modify_font(Pango.FontDescription(font_desc))
self.items.set_sensitive(False)
self.items_value.set_sensitive(False)
info_col_2 = Gtk.HBox()
info_col_2.pack_start(self.bin_name, False, True, 0)
info_col_2.pack_start(Gtk.Label(), True, True, 0)
info_col_3 = Gtk.HBox()
info_col_3.pack_start(self.items, False, False, 0)
info_col_3.pack_start(self.items_value, False, False, 0)
info_col_3.pack_start(Gtk.Label(), True, True, 0)
PAD_HEIGHT = 2
self.pack_start(guiutils.pad_label(24, 4), False, False, 0)
self.pack_start(info_col_2, False, False, 0)
self.pack_start(guiutils.pad_label(12, 4), False, False, 0)
self.pack_start(info_col_3, False, False, 0)
self.pack_start(Gtk.Label(), True, True, 0)
self.set_spacing(4)
def display_bin_info(self):
self.bin_name.set_text("" + editorstate.PROJECT().c_bin.name + "")
self.items.set_text(_("Items:") + " ")
self.items_value.set_text(str(len(editorstate.PROJECT().c_bin.file_ids)))
self._set_use_mark_up()
def _set_use_mark_up(self):
self.bin_name.set_use_markup(True)
self.items.set_use_markup(True)
self.items_value.set_use_markup(True)
# -------------------------------------------- media select panel
class MediaPanel():
def __init__(self, media_file_popup_cb, double_click_cb, panel_menu_cb):
# Aug-2019 - SvdB - BB
self.widget = Gtk.VBox()
self.row_widgets = []
self.selected_objects = []
self.columns = editorpersistance.prefs.media_columns
self.media_file_popup_cb = media_file_popup_cb
self.panel_menu_cb = panel_menu_cb
self.double_click_cb = double_click_cb
self.monitor_indicator = guiutils.get_cairo_image("monitor_indicator", force=False) # Aug-2019 - SvdB - BB - We want to keep the small icon for this
self.last_event_time = 0.0
self.last_ctrl_selected_media_object = None
self.double_click_release = False # needed to get focus over to pos bar after double click, usually media object grabs focus
global has_proxy_icon, is_proxy_icon, graphics_icon, imgseq_icon, audio_icon, pattern_icon, profile_warning_icon
has_proxy_icon = guiutils.get_cairo_image("has_proxy_indicator")
is_proxy_icon = guiutils.get_cairo_image("is_proxy_indicator")
graphics_icon = guiutils.get_cairo_image("graphics_indicator")
imgseq_icon = guiutils.get_cairo_image("imgseq_indicator")
audio_icon = guiutils.get_cairo_image("audio_indicator")
pattern_icon = guiutils.get_cairo_image("pattern_producer_indicator")
profile_warning_icon = guiutils.get_cairo_image("profile_warning")
def get_selected_media_objects(self):
return self.selected_objects
def media_object_selected(self, media_object, widget, event):
if event.type == Gdk.EventType._2BUTTON_PRESS:
self.double_click_release = True
self.clear_selection()
media_object.widget.override_background_color(Gtk.StateType.NORMAL, gui.get_selected_bg_color())
self.selected_objects.append(media_object)
self.widget.queue_draw()
gui.pos_bar.widget.grab_focus()
GLib.idle_add(self.double_click_cb, media_object.media_file)
return
# HACK! We're using event times to exclude double events when icon is pressed
now = time.time()
if (now - self.last_event_time) < 0.05:
self.last_event_time = now
return
self.last_event_time = now
widget.grab_focus()
if event.button == 1:
if (event.get_state() & Gdk.ModifierType.CONTROL_MASK):
# add to selected if not there
try:
index = self.selected_objects.index(media_object)
except:
self.selected_objects.append(media_object)
media_object.widget.override_background_color(Gtk.StateType.NORMAL, gui.get_selected_bg_color())
self.last_ctrl_selected_media_object = media_object
return
elif (event.get_state() & Gdk.ModifierType.SHIFT_MASK) and len(self.selected_objects) > 0:
# Get data on current selection and pressed media object
first_selected = -1
last_selected = -1
pressed_widget = -1
for i in range(0, len(current_bin().file_ids)):
file_id = current_bin().file_ids[i]
m_file = PROJECT().media_files[file_id]
m_obj = self.widget_for_mediafile[m_file]
if m_obj in self.selected_objects:
selected = True
else:
selected = False
if selected and first_selected == -1:
first_selected = i
if selected:
last_selected = i
if media_object == m_obj:
pressed_widget = i
# Get new selection range
if pressed_widget < first_selected:
sel_range = (pressed_widget, first_selected)
elif pressed_widget > last_selected:
sel_range = (last_selected, pressed_widget)
else:
sel_range = (pressed_widget, pressed_widget)
self.clear_selection()
# Select new range
start, end = sel_range
for i in range(start, end + 1):
file_id = current_bin().file_ids[i]
m_file = PROJECT().media_files[file_id]
m_obj = self.widget_for_mediafile[m_file]
self.selected_objects.append(m_obj)
m_obj.widget.override_background_color(Gtk.StateType.NORMAL, gui.get_selected_bg_color())
else:
self.clear_selection()
media_object.widget.override_background_color(Gtk.StateType.NORMAL, gui.get_selected_bg_color())
self.selected_objects.append(media_object)
elif event.button == 3:
self.clear_selection()
display_media_file_popup_menu(media_object.media_file,
self.media_file_popup_cb,
event)
self.widget.queue_draw()
def release_on_media_object(self, media_object, widget, event):
if self.last_ctrl_selected_media_object == media_object:
self.last_ctrl_selected_media_object = None
return
if not self.double_click_release:
widget.grab_focus()
else:
self.double_click_release = False # after double click we want bos bar to have focus
if event.button == 1:
if (event.get_state() & Gdk.ModifierType.CONTROL_MASK):
# remove from selected if already there
try:
index = self.selected_objects.index(media_object)
self.selected_objects.remove(media_object)
media_object.widget.override_background_color(Gtk.StateType.NORMAL, gui.get_bg_color())
except:
pass
def select_media_file(self, media_file):
self.clear_selection()
self.selected_objects.append(self.widget_for_mediafile[media_file])
def select_media_file_list(self, media_files):
self.clear_selection()
for media_file in media_files:
self.selected_objects.append(self.widget_for_mediafile[media_file])
def update_selected_bg_colors(self):
bg_color = gui.get_selected_bg_color()
for media_object in self.selected_objects:
media_object.widget.override_background_color(Gtk.StateType.NORMAL, bg_color)
def empty_pressed(self, widget, event):
self.clear_selection()
if event.button == 3:
self.panel_menu_cb(event)
def select_all(self):
self.clear_selection()
bg_color = gui.get_selected_bg_color()
for media_file, media_object in self.widget_for_mediafile.items():
media_object.widget.override_background_color(Gtk.StateType.NORMAL, bg_color)
self.selected_objects.append(media_object)
def clear_selection(self):
bg_color = gui.get_bg_color()
for m_obj in self.selected_objects:
m_obj.widget.override_background_color(Gtk.StateType.NORMAL, bg_color)
self.selected_objects = []
def columns_changed(self, columns):
self.columns = columns
editorpersistance.prefs.media_columns = self.columns
editorpersistance.save()
self.fill_data_model()
def fill_data_model(self):
for w in self.row_widgets:
self.widget.remove(w)
self.row_widgets = []
self.widget_for_mediafile = {}
self.selected_objects = []
# info with text for empty panel
if len(current_bin().file_ids) == 0:
filler = self._get_empty_filler()
dnd.connect_media_drop_widget(filler)
self.row_widgets.append(filler)
self.widget.pack_start(filler, True, True, 0)
info = Gtk.Label(_("Right Click to Add Media."))
info.set_sensitive(False)
dnd.connect_media_drop_widget(info)
filler = self._get_empty_filler(info)
self.widget.pack_start(filler, False, False, 0)
self.row_widgets.append(filler)
filler = self._get_empty_filler()
dnd.connect_media_drop_widget(filler)
self.row_widgets.append(filler)
self.widget.pack_start(filler, True, True, 0)
self.widget.show_all()
return
column = 0
bin_index = 0
row_box = Gtk.HBox()
dnd.connect_media_drop_widget(row_box)
row_box.set_size_request(MEDIA_OBJECT_WIDGET_WIDTH * self.columns, MEDIA_OBJECT_WIDGET_HEIGHT)
for file_id in current_bin().file_ids:
media_file = PROJECT().media_files[file_id]
# Filter view
if ((editorstate.media_view_filter == appconsts.SHOW_VIDEO_FILES)
and (media_file.type != appconsts.VIDEO)):
continue
if ((editorstate.media_view_filter == appconsts.SHOW_AUDIO_FILES)
and (media_file.type != appconsts.AUDIO)):
continue
if ((editorstate.media_view_filter == appconsts.SHOW_GRAPHICS_FILES)
and (media_file.type != appconsts.IMAGE)):
continue
if ((editorstate.media_view_filter == appconsts.SHOW_IMAGE_SEQUENCES)
and (media_file.type != appconsts.IMAGE_SEQUENCE)):
continue
if ((editorstate.media_view_filter == appconsts.SHOW_PATTERN_PRODUCERS)
and (media_file.type != appconsts.PATTERN_PRODUCER)):
continue
media_object = MediaObjectWidget(media_file, self.media_object_selected, self.release_on_media_object, bin_index, self.monitor_indicator)
dnd.connect_media_files_object_widget(media_object.widget)
dnd.connect_media_files_object_cairo_widget(media_object.img)
self.widget_for_mediafile[media_file] = media_object
row_box.pack_start(media_object.widget, False, False, 0)
column += 1
if column == self.columns:
filler = self._get_empty_filler()
row_box.pack_start(filler, True, True, 0)
self.widget.pack_start(row_box, False, False, 0)
self.row_widgets.append(row_box)
row_box = Gtk.HBox()
column = 0
bin_index += 1
if column != 0:
filler = self._get_empty_filler()
dnd.connect_media_drop_widget(filler)
row_box.pack_start(filler, True, True, 0)
self.widget.pack_start(row_box, False, False, 0)
self.row_widgets.append(row_box)
filler = self._get_empty_filler()
dnd.connect_media_drop_widget(filler)
self.row_widgets.append(filler)
self.widget.pack_start(filler, True, True, 0)
self.widget.show_all()
def _get_empty_filler(self, widget=None):
filler = Gtk.EventBox()
filler.connect("button-press-event", lambda w,e: self.empty_pressed(w,e))
if widget == None:
filler.add(Gtk.Label())
else:
filler.add(widget)
return filler
class MediaObjectWidget:
def __init__(self, media_file, selected_callback, release_callback, bin_index, indicator_icon):
self.media_file = media_file
self.selected_callback = selected_callback
self.bin_index = bin_index
self.indicator_icon = indicator_icon
self.selected_callback = selected_callback
self.matches_project_profile = media_file.matches_project_profile()
self.widget = Gtk.EventBox()
self.widget.connect("button-press-event", lambda w,e: selected_callback(self, w, e))
self.widget.connect("button-release-event", lambda w,e: release_callback(self, w, e))
self.widget.dnd_media_widget_attr = True # this is used to identify widget at dnd drop
self.widget.set_can_focus(True)
self.widget.add_events(Gdk.EventMask.KEY_PRESS_MASK)
self.vbox = Gtk.VBox()
self.img = cairoarea.CairoDrawableArea2(appconsts.THUMB_WIDTH, appconsts.THUMB_HEIGHT, self._draw_icon)
self.img.press_func = self._press
self.img.dnd_media_widget_attr = True # this is used to identify widget at dnd drop
self.img.set_can_focus(True)
self.img.set_tooltip_text(media_file.name)
txt = Gtk.Label(label=media_file.name)
txt.modify_font(Pango.FontDescription("sans 9"))
txt.set_max_width_chars(13)
# Feb-2017 - SvdB - For full file names. First part shows the original code for short file names
if editorpersistance.prefs.show_full_file_names == False:
txt.set_ellipsize(Pango.EllipsizeMode.END)
else:
txt.set_line_wrap_mode(Pango.WrapMode.CHAR)
txt.set_line_wrap(True)
# end SvdB
txt.set_tooltip_text(media_file.name)
self.vbox.pack_start(self.img, True, True, 0)
self.vbox.pack_start(txt, False, False, 0)
self.align = guiutils.set_margins(self.vbox, 6, 6, 6, 6)
self.widget.add(self.align)
def _get_matches_profile(self):
if (not hasattr(self.media_file, "info")): # to make really sure that old projects don't crash,
return True # but probably is not needed as attr is added at load
if self.media_file.info == None:
return True
is_match = True # this is true for audio and graphics and image sequences and is only
# set false for video that does not match profile
if self.media_file.type == appconsts.VIDEO:
best_media_profile_index = mltprofiles.get_closest_matching_profile_index(self.media_file.info)
project_profile_index = mltprofiles.get_index_for_name(PROJECT().profile.description())
if best_media_profile_index != project_profile_index:
is_match = False
return is_match
def _press(self, event):
self.selected_callback(self, self.widget, event)
def _draw_icon(self, event, cr, allocation):
x, y, w, h = allocation
self.create_round_rect_path(cr, 0, 0, w - 5, h - 5, 6.0)
cr.clip()
cr.set_source_surface(self.media_file.icon, 0, 0)
cr.paint()
cr.reset_clip()
cr.set_source_rgba(0,0,0,0.3)
cr.set_line_width(2.0)
self.create_round_rect_path(cr, 0, 0, w - 5, h - 5, 6.0)
cr.stroke()
if self.media_file == editorstate.MONITOR_MEDIA_FILE():
cr.set_source_surface(self.indicator_icon, 29, 22)
cr.paint()
cr.select_font_face ("sans-serif",
cairo.FONT_SLANT_NORMAL,
cairo.FONT_WEIGHT_NORMAL)
cr.set_font_size(9)
if self.media_file.mark_in != -1 and self.media_file.mark_out != -1:
cr.set_source_rgba(0,0,0,0.5)
cr.rectangle(21,1,72,12)
cr.fill()
cr.move_to(23, 10)
clip_length = utils.get_tc_string(self.media_file.mark_out - self.media_file.mark_in + 1) #+1 out incl.
cr.set_source_rgb(1, 1, 1)
cr.show_text("][ " + str(clip_length))
cr.set_source_rgba(0,0,0,0.5)
cr.rectangle(28,71,62,12)
cr.fill()
cr.move_to(30, 79)
cr.set_source_rgb(1, 1, 1)
media_length = utils.get_tc_string(self.media_file.length)
cr.show_text(str(media_length))
if self.media_file.type != appconsts.PATTERN_PRODUCER:
if self.media_file.is_proxy_file == True:
cr.set_source_surface(is_proxy_icon, 96, 6)
cr.paint()
elif self.media_file.has_proxy_file == True:
cr.set_source_surface(has_proxy_icon, 96, 6)
cr.paint()
if self.matches_project_profile == False:
cr.set_source_surface(profile_warning_icon, 4, 70)
cr.paint()
if self.media_file.type == appconsts.IMAGE:
cr.set_source_surface(graphics_icon, 6, 6)
cr.paint()
if self.media_file.type == appconsts.IMAGE_SEQUENCE:
cr.set_source_surface(imgseq_icon, 6, 6)
cr.paint()
if self.media_file.type == appconsts.AUDIO:
cr.set_source_surface(audio_icon, 6, 6)
cr.paint()
if self.media_file.type == appconsts.PATTERN_PRODUCER:
cr.set_source_surface(pattern_icon, 6, 6)
cr.paint()
def create_round_rect_path(self, cr, x, y, width, height, radius=4.0):
degrees = math.pi / 180.0
cr.new_sub_path()
cr.arc(x + width - radius, y + radius, radius, -90 * degrees, 0 * degrees)
cr.arc(x + width - radius, y + height - radius, radius, 0 * degrees, 90 * degrees)
cr.arc(x + radius, y + height - radius, radius, 90 * degrees, 180 * degrees)
cr.arc(x + radius, y + radius, radius, 180 * degrees, 270 * degrees)
cr.close_path()
# -------------------------------------------- context menus
class EditorSeparator:
"""
GUI component used to add, move and remove keyframes to of
inside a single clip. Does not a reference of the property being
edited and needs a parent editor to write keyframe values.
"""
def __init__(self):
self.widget = cairoarea.CairoDrawableArea2( SEPARATOR_WIDTH,
SEPARATOR_HEIGHT,
self._draw)
def _draw(self, event, cr, allocation):
"""
Callback for repaint from CairoDrawableArea.
We get cairo contect and allocation.
"""
x, y, w, h = allocation
# Draw separator
cr.set_line_width(1.0)
cr.set_source_rgba(0.5,0.5,0.5,0.2)
cr.move_to(8.5, 2.5)
cr.line_to(w - 8.5, 2.5)
cr.stroke()
# ---------------------------------------------- MISC WIDGETS
def get_monitor_view_select_combo(callback):
# Aug-2019 - SvdB - BB
prefs = editorpersistance.prefs
size_adj = 1
if prefs.double_track_hights:
size_adj = 2
surface_list = [guiutils.get_cairo_image("program_view_2"),
guiutils.get_cairo_image("vectorscope"),
guiutils.get_cairo_image("rgbparade")]
menu_launch = ImageMenuLaunch(callback, surface_list, w=24*size_adj, h=20*size_adj)
menu_launch.surface_y = 8*size_adj
return menu_launch
def get_trim_view_select_combo(callback):
# Aug-2019 - SvdB - BB
prefs = editorpersistance.prefs
size_adj = 1
if prefs.double_track_hights:
size_adj = 2
surface = guiutils.get_cairo_image("trim_view")
menu_launch = PressLaunch(callback, surface, w=24*size_adj, h=20*size_adj)
menu_launch.surface_y = 8*size_adj
return menu_launch
def get_compositor_track_select_combo(source_track, target_track, callback):
tracks_combo = Gtk.ComboBoxText()
active_index = -1
cb_index = 0
for track_index in range(source_track.id - 1, current_sequence().first_video_index - 1, -1):
track = current_sequence().tracks[track_index]
tracks_combo.append_text(utils.get_track_name(track, current_sequence()))
if track == target_track:
active_index = cb_index
cb_index += 1
if active_index == -1:
tracks_combo.set_active(0)
else:
tracks_combo.set_active(active_index)
tracks_combo.connect("changed", lambda w,e: callback(w), None)
return tracks_combo
# -------------------------------------------- context menus
def display_tracks_popup_menu(event, track, callback):
track_obj = current_sequence().tracks[track]
track_menu = tracks_pop_menu
guiutils.remove_children(track_menu)
if track_obj.edit_freedom != appconsts.FREE:
track_menu.append(_get_menu_item(_("Lock Track"), callback, (track,"lock", None), False))
track_menu.append(_get_menu_item(_("Unlock Track"), callback, (track,"unlock", None), True))
else:
track_menu.append(_get_menu_item(_("Lock Track"), callback, (track,"lock", None), True))
track_menu.append(_get_menu_item(_("Unlock Track"), callback, (track,"unlock", None), False))
_add_separetor(track_menu)
normal_size_item = Gtk.RadioMenuItem()
normal_size_item.set_label(_("Large Height"))
normal_size_item.set_active(track_obj.height == appconsts.TRACK_HEIGHT_NORMAL)
normal_size_item.connect("activate", callback, (track, "normal_height", None))
track_menu.append(normal_size_item)
small_size_item = Gtk.RadioMenuItem.new_with_label([normal_size_item], _("Normal Height"))
small_size_item.set_active(track_obj.height != appconsts.TRACK_HEIGHT_NORMAL)
small_size_item.connect("activate", callback, (track, "small_height", None))
track_menu.append(small_size_item)
_add_separetor(track_menu)
track_menu.append(_get_track_mute_menu_item(event, track_obj, callback))
track_menu.show_all()
track_menu.popup(None, None, None, None, event.button, event.time)
def display_clip_popup_menu(event, clip, track, callback):
if clip.is_blanck_clip:
display_blank_clip_popup_menu(event, clip, track, callback)
return
if hasattr(clip, "rendered_type"):
display_transition_clip_popup_menu(event, clip, track, callback)
return
clip_menu = clip_popup_menu
guiutils.remove_children(clip_menu)
# Menu items
clip_menu.add(_get_menu_item(_("Open in Filters Editor"), callback, (clip, track, "open_in_editor", event.x)))
# Only make opening in compositor editor for video tracks V2 and higher
if track.id <= current_sequence().first_video_index:
active = False
else:
active = True
if clip.media_type != appconsts.PATTERN_PRODUCER:
clip_menu.add(_get_menu_item(_("Open in Clip Monitor"), callback,\
(clip, track, "open_in_clip_monitor", event.x)))
_add_separetor(clip_menu)
if track.type == appconsts.VIDEO:
clip_menu.add(_get_tool_integration_menu_item(event, clip, track, callback))
_add_separetor(clip_menu)
if track.type == appconsts.VIDEO:
active = True
if clip.media_type == appconsts.IMAGE_SEQUENCE or clip.media_type == appconsts.IMAGE or clip.media_type == appconsts.PATTERN_PRODUCER:
active = False
clip_menu.add(_get_menu_item(_("Split Audio"), callback,\
(clip, track, "split_audio", event.x), active))
if track.id == current_sequence().first_video_index:
active = True
else:
active = False
if clip.media_type == appconsts.IMAGE_SEQUENCE or clip.media_type == appconsts.IMAGE or clip.media_type == appconsts.PATTERN_PRODUCER:
active = False
clip_menu.add(_get_menu_item(_("Split Audio Synched"), callback,\
(clip, track, "split_audio_synched", event.x), active))
if editorstate.display_all_audio_levels == False:
_add_separetor(clip_menu)
if clip.waveform_data == None:
clip_menu.add(_get_menu_item(_("Display Audio Level"), callback,\
(clip, track, "display_waveform", event.x), True))
else:
clip_menu.add(_get_menu_item(_("Clear Waveform"), callback,\
(clip, track, "clear_waveform", event.x), True))
audio_sync_item = _get_menu_item(_("Select Clip to Audio Sync With..."), callback, (clip, track, "set_audio_sync_clip", event.x))
if utils.is_mlt_xml_file(clip.path) == True:
audio_sync_item.set_sensitive(False)
if clip.media_type == appconsts.IMAGE_SEQUENCE or clip.media_type == appconsts.IMAGE or clip.media_type == appconsts.PATTERN_PRODUCER:
audio_sync_item.set_sensitive(False)
clip_menu.add(audio_sync_item)
_add_separetor(clip_menu)
if track.id != current_sequence().first_video_index:
if clip.sync_data != None:
clip_menu.add(_get_menu_item(_("Resync"), callback, (clip, track, "resync", event.x)))
clip_menu.add(_get_menu_item(_("Clear Sync Relation"), callback, (clip, track, "clear_sync_rel", event.x)))
else:
clip_menu.add(_get_menu_item(_("Select Sync Parent Clip..."), callback, (clip, track, "set_master", event.x)))
_add_separetor(clip_menu)
clip_menu.add(_get_mute_menu_item(event, clip, track, callback))
_add_separetor(clip_menu)
clip_menu.add(_get_filters_add_menu_item(event, clip, track, callback))
_add_separetor(clip_menu)
# Only add compositors for video tracks V2 and higher
if track.id <= current_sequence().first_video_index:
active = False
else:
active = True
compositors_add_item = _get_compositors_add_menu_item(event, clip, track, callback, active)
if (current_sequence().compositing_mode == appconsts.COMPOSITING_MODE_STANDARD_AUTO_FOLLOW
and len(current_sequence().get_clip_compositors(clip)) != 0):
compositors_add_item.set_sensitive(False)
clip_menu.add(compositors_add_item)
if current_sequence().compositing_mode != appconsts.COMPOSITING_MODE_STANDARD_AUTO_FOLLOW:
clip_menu.add(_get_auto_fade_compositors_add_menu_item(event, clip, track, callback, active))
if current_sequence().compositing_mode == appconsts.COMPOSITING_MODE_STANDARD_AUTO_FOLLOW:
item_text = _("Delete Compositor")
else:
item_text = _("Delete Compositor/s")
comp_delete_item = _get_menu_item(item_text, callback, (clip, track, "delete_compositors", event.x))
if len(current_sequence().get_clip_compositors(clip)) == 0:
comp_delete_item.set_sensitive(False)
clip_menu.add(comp_delete_item)
_add_separetor(clip_menu)
clip_menu.add(_get_clone_filters_menu_item(event, clip, track, callback))
clip_menu.add(_get_menu_item(_("Clear Filters"), callback, (clip, track, "clear_filters", event.x)))
_add_separetor(clip_menu)
clip_menu.add(_get_clip_properties_menu_item(event, clip, track, callback))
clip_menu.add(_get_clip_markers_menu_item(event, clip, track, callback))
clip_menu.add(_get_menu_item(_("Clip Info"), callback,\
(clip, track, "clip_info", event.x)))
_add_separetor(clip_menu)
clip_menu.add(_get_select_menu_item(event, clip, track, callback))
if track.type == appconsts.VIDEO and clip.media_type != appconsts.PATTERN_PRODUCER:
_add_separetor(clip_menu)
clip_menu.add(_get_match_frame_menu_item(event, clip, track, callback))
_add_separetor(clip_menu)
clip_menu.add(_get_edit_menu_item(event, clip, track, callback))
clip_menu.popup(None, None, None, None, event.button, event.time)
def display_transition_clip_popup_menu(event, clip, track, callback):
clip_menu = transition_clip_menu
guiutils.remove_children(clip_menu)
clip_menu.add(_get_menu_item(_("Rerender"), callback, (clip, track, "re_render", event.x)))
clip_menu.add(_get_menu_item(_("Open in Filters Editor"), callback, (clip, track, "open_in_editor", event.x)))
_add_separetor(clip_menu)
clip_menu.add(_get_mute_menu_item(event, clip, track, callback))
_add_separetor(clip_menu)
clip_menu.add(_get_filters_add_menu_item(event, clip, track, callback))
# Only add compositors for video tracks V2 and higher
if track.id <= current_sequence().first_video_index:
active = False
else:
active = True
compositors_add_item = _get_compositors_add_menu_item(event, clip, track, callback, active)
if (current_sequence().compositing_mode == appconsts.COMPOSITING_MODE_STANDARD_AUTO_FOLLOW
and len(current_sequence().get_clip_compositors(clip)) != 0):
compositors_add_item.set_sensitive(False)
clip_menu.add(compositors_add_item)
clip_menu.add(_get_blenders_add_menu_item(event, clip, track, callback, active))
_add_separetor(clip_menu)
clip_menu.add(_get_clone_filters_menu_item(event, clip, track, callback))
clip_menu.add(_get_menu_item(_("Clear Filters"), callback, (clip, track, "clear_filters", event.x)))
clip_menu.popup(None, None, None, None, event.button, event.time)
def display_blank_clip_popup_menu(event, clip, track, callback):
clip_menu = blank_clip_menu
guiutils.remove_children(clip_menu)
clip_menu.add(_get_menu_item(_("Strech Prev Clip to Cover"), callback, (clip, track, "cover_with_prev", event.x)))
clip_menu.add(_get_menu_item(_("Strech Next Clip to Cover"), callback, (clip, track, "cover_with_next", event.x)))
_add_separetor(clip_menu)
clip_menu.add(_get_menu_item(_("Delete"), callback, (clip, track, "delete_blank", event.x)))
clip_menu.popup(None, None, None, None, event.button, event.time)
def display_audio_clip_popup_menu(event, clip, track, callback):
if clip.is_blanck_clip:
display_blank_clip_popup_menu(event, clip, track, callback)
return
clip_menu = audio_clip_menu
guiutils.remove_children(clip_menu)
clip_menu.add(_get_menu_item(_("Open in Filters Editor"), callback, (clip, track, "open_in_editor", event.x)))
if clip.media_type != appconsts.PATTERN_PRODUCER:
clip_menu.add(_get_menu_item(_("Open in Clip Monitor"), callback,\
(clip, track, "open_in_clip_monitor", event.x)))
_add_separetor(clip_menu)
if clip.sync_data != None:
clip_menu.add(_get_menu_item(_("Resync"), callback, (clip, track, "resync", event.x)))
clip_menu.add(_get_menu_item(_("Clear Sync Relation"), callback, (clip, track, "clear_sync_rel", event.x)))
else:
clip_menu.add(_get_menu_item(_("Select Sync Parent Clip..."), callback, (clip, track, "set_master", event.x)))
_add_separetor(clip_menu)
if clip.waveform_data == None:
clip_menu.add(_get_menu_item(_("Display Audio Level"), callback,\
(clip, track, "display_waveform", event.x), True))
else:
clip_menu.add(_get_menu_item(_("Clear Waveform"), callback,\
(clip, track, "clear_waveform", event.x), True))
_add_separetor(clip_menu)
clip_menu.add(_get_mute_menu_item(event, clip, track, callback))
_add_separetor(clip_menu)
clip_menu.add(_get_audio_filters_add_menu_item(event, clip, track, callback))
_add_separetor(clip_menu)
clip_menu.add(_get_menu_item(_("Rename Clip"), callback,\
(clip, track, "rename_clip", event.x)))
clip_menu.add(_get_color_menu_item(clip, track, callback))
clip_menu.add(_get_menu_item(_("Clip Info"), callback,\
(clip, track, "clip_info", event.x)))
_add_separetor(clip_menu)
clip_menu.add(_get_select_menu_item(event, clip, track, callback))
_add_separetor(clip_menu)
clip_menu.add(_get_edit_menu_item(event, clip, track, callback))
clip_menu.popup(None, None, None, None, event.button, event.time)
def display_compositor_popup_menu(event, compositor, callback):
compositor_menu = compositor_popup_menu
guiutils.remove_children(compositor_menu)
compositor_menu.add(_get_menu_item(_("Open In Compositor Editor"), callback, ("open in editor",compositor)))
_add_separetor(compositor_menu)
compositor_menu.add(_get_menu_item(_("Sync with Origin Clip"), callback, ("sync with origin",compositor)))
autofollow_item = Gtk.CheckMenuItem()
autofollow_item.set_label(_("Obey Auto Follow"))
autofollow_item.set_active(compositor.obey_autofollow)
autofollow_item.connect("activate", callback, ("set auto follow", compositor))
autofollow_item.set_sensitive(editorstate.auto_follow_active())
autofollow_item.show()
compositor_menu.append(autofollow_item)
_add_separetor(compositor_menu)
compositor_menu.add(_get_menu_item(_("Delete"), callback, ("delete",compositor)))
compositor_menu.popup(None, None, None, None, event.button, event.time)
def _get_filters_add_menu_item(event, clip, track, callback):
menu_item = Gtk.MenuItem(_("Add Filter"))
sub_menu = Gtk.Menu()
menu_item.set_submenu(sub_menu)
for group in mltfilters.groups:
group_name, filters_array = group
group_item = Gtk.MenuItem(group_name)
sub_menu.append(group_item)
sub_sub_menu = Gtk.Menu()
group_item.set_submenu(sub_sub_menu)
for filter_info in filters_array:
filter_item = Gtk.MenuItem(translations.get_filter_name(filter_info.name))
sub_sub_menu.append(filter_item)
filter_item.connect("activate", callback, (clip, track, "add_filter", (event.x, filter_info)))
filter_item.show()
group_item.show()
menu_item.show()
return menu_item
def _get_audio_filters_add_menu_item(event, clip, track, callback):
menu_item = Gtk.MenuItem(_("Add Filter"))
sub_menu = Gtk.Menu()
menu_item.set_submenu(sub_menu)
audio_groups = mltfilters.get_audio_filters_groups()
for group in audio_groups:
if group == None:
continue
group_name, filters_array = group
group_item = Gtk.MenuItem(group_name)
sub_menu.append(group_item)
sub_sub_menu = Gtk.Menu()
group_item.set_submenu(sub_sub_menu)
for filter_info in filters_array:
filter_item = Gtk.MenuItem(translations.get_filter_name(filter_info.name))
sub_sub_menu.append(filter_item)
filter_item.connect("activate", callback, (clip, track, "add_filter", (event.x, filter_info)))
filter_item.show()
group_item.show()
menu_item.show()
return menu_item
def _get_compositors_add_menu_item(event, clip, track, callback, sensitive):
menu_item = Gtk.MenuItem(_("Add Compositor"))
sub_menu = Gtk.Menu()
menu_item.set_submenu(sub_menu)
for i in range(0, len(mlttransitions.compositors)):
compositor = mlttransitions.compositors[i]
name, compositor_type = compositor
# Continue if dropped compositor
if compositor_type in mlttransitions.dropped_compositors:
continue
# Continue if compositor_type not present in system
try:
info = mlttransitions.mlt_compositor_transition_infos[compositor_type]
except:
continue
compositor_item = Gtk.MenuItem(name)
sub_menu.append(compositor_item)
compositor_item.connect("activate", callback, (clip, track, "add_compositor", (event.x, compositor_type)))
compositor_item.show()
_add_separetor(sub_menu)
if current_sequence().compositing_mode != appconsts.COMPOSITING_MODE_STANDARD_AUTO_FOLLOW:
alpha_combiners_menu_item = _get_alpha_combiners_add_menu_item(event, clip, track, callback, sensitive)
sub_menu.append(alpha_combiners_menu_item)
blenders_menu_item = _get_blenders_add_menu_item(event, clip, track, callback, sensitive)
sub_menu.append(blenders_menu_item)
wipe_compositors_menu_item = _get_wipe_compositors_add_menu_item(event, clip, track, callback, sensitive)
sub_menu.append(wipe_compositors_menu_item)
menu_item.set_sensitive(sensitive)
menu_item.show()
return menu_item
def _get_blenders_add_menu_item(event, clip, track, callback, sensitive):
menu_item = Gtk.MenuItem(_("Blenders"))
sub_menu = Gtk.Menu()
menu_item.set_submenu(sub_menu)
for i in range(0, len(mlttransitions.blenders)):
blend = mlttransitions.blenders[i]
name, compositor_type = blend
if compositor_type in mlttransitions.dropped_compositors:
continue
blender_item = Gtk.MenuItem(name)
sub_menu.append(blender_item)
blender_item.connect("activate", callback, (clip, track, "add_compositor", (event.x, compositor_type)))
blender_item.show()
menu_item.set_sensitive(sensitive)
menu_item.show()
return menu_item
def _get_alpha_combiners_add_menu_item(event, clip, track, callback, sensitive):
menu_item = Gtk.MenuItem(_("Alpha"))
sub_menu = Gtk.Menu()
menu_item.set_submenu(sub_menu)
for i in range(0, len(mlttransitions.alpha_combiners)):
alpha_combiner = mlttransitions.alpha_combiners[i]
name, compositor_type = alpha_combiner
alpha_combiner_item = Gtk.MenuItem(name)
sub_menu.append(alpha_combiner_item)
alpha_combiner_item.connect("activate", callback, (clip, track, "add_compositor", (event.x, compositor_type)))
alpha_combiner_item.show()
menu_item.set_sensitive(sensitive)
menu_item.show()
return menu_item
def _get_wipe_compositors_add_menu_item(event, clip, track, callback, sensitive):
menu_item = Gtk.MenuItem(_("Wipe"))
sub_menu = Gtk.Menu()
menu_item.set_submenu(sub_menu)
for i in range(0, len(mlttransitions.wipe_compositors)):
alpha_combiner = mlttransitions.wipe_compositors[i]
name, compositor_type = alpha_combiner
wipe_item = Gtk.MenuItem(name)
sub_menu.append(wipe_item)
wipe_item.connect("activate", callback, (clip, track, "add_compositor", (event.x, compositor_type)))
wipe_item.show()
menu_item.set_sensitive(sensitive)
menu_item.show()
return menu_item
def _get_auto_fade_compositors_add_menu_item(event, clip, track, callback, sensitive):
menu_item = Gtk.MenuItem(_("Add Fade"))
sub_menu = Gtk.Menu()
menu_item.set_submenu(sub_menu)
for i in range(0, len(mlttransitions.autofades)):
auto_fade_compositor = mlttransitions.autofades[i]
name, compositor_type = auto_fade_compositor
try:
info = mlttransitions.mlt_compositor_transition_infos[compositor_type]
except:
continue
compositor_item = Gtk.MenuItem(name)
sub_menu.append(compositor_item)
compositor_item.connect("activate", callback, (clip, track, "add_autofade", (event.x, compositor_type)))
compositor_item.show()
menu_item.set_sensitive(sensitive)
menu_item.show()
return menu_item
def _get_match_frame_menu_item(event, clip, track, callback):
menu_item = Gtk.MenuItem(_("Show Match Frame"))
sub_menu = Gtk.Menu()
menu_item.set_submenu(sub_menu)
start_item_monitor = Gtk.MenuItem(_("First Frame in Monitor"))
sub_menu.append(start_item_monitor)
start_item_monitor.connect("activate", callback, (clip, track, "match_frame_start_monitor", None))
start_item_monitor.show()
end_item_monitor = Gtk.MenuItem(_("Last Frame in Monitor"))
sub_menu.append(end_item_monitor)
end_item_monitor.connect("activate", callback, (clip, track, "match_frame_end_monitor", None))
end_item_monitor.show()
_add_separetor(sub_menu)
start_item = Gtk.MenuItem(_("First Frame on Timeline"))
sub_menu.append(start_item)
start_item.connect("activate", callback, (clip, track, "match_frame_start", None))
start_item.show()
end_item = Gtk.MenuItem(_("Last Frame on Timeline"))
sub_menu.append(end_item)
end_item.connect("activate", callback, (clip, track, "match_frame_end", None))
end_item.show()
_add_separetor(sub_menu)
clear_item = Gtk.MenuItem(_("Clear Match Frame"))
sub_menu.append(clear_item)
clear_item.connect("activate", callback, (clip, track, "match_frame_close", None))
clear_item.show()
menu_item.set_sensitive(True)
menu_item.show()
return menu_item
def _get_select_menu_item(event, clip, track, callback):
menu_item = Gtk.MenuItem(_("Select"))
sub_menu = Gtk.Menu()
menu_item.set_submenu(sub_menu)
all_after = Gtk.MenuItem(_("All Clips After"))
sub_menu.append(all_after)
all_after.connect("activate", callback, (clip, track, "select_all_after", None))
all_after.show()
all_before = Gtk.MenuItem(_("All Clips Before"))
sub_menu.append(all_before)
all_before.connect("activate", callback, (clip, track, "select_all_before", None))
all_before.show()
menu_item.set_sensitive(True)
menu_item.show()
return menu_item
def _get_tool_integration_menu_item(event, clip, track, callback):
menu_item = Gtk.MenuItem(_("Export To Tool"))
sub_menu = Gtk.Menu()
menu_item.set_submenu(sub_menu)
export_tools = toolsintegration.get_export_integrators()
for integrator in export_tools:
export_item = Gtk.MenuItem(copy.copy(integrator.tool_name))
sub_menu.append(export_item)
export_item.connect("activate", integrator.export_callback, (clip, track))
if integrator.supports_clip_media(clip) == False:
export_item.set_sensitive(False)
export_item.show()
menu_item.show()
return menu_item
def _get_edit_menu_item(event, clip, track, callback):
menu_item = Gtk.MenuItem(_("Edit"))
sub_menu = Gtk.Menu()
menu_item.set_submenu(sub_menu)
if (clip.media_type == appconsts.IMAGE_SEQUENCE or clip.media_type == appconsts.IMAGE or clip.media_type == appconsts.PATTERN_PRODUCER) == False:
vol_item = _get_menu_item(_("Volume Keyframes"), callback, (clip, track, "volumekf", event.x))
sub_menu.append(vol_item)
if track.type == appconsts.VIDEO:
bright_item = _get_menu_item(_("Brightness Keyframes"), callback, (clip, track, "brightnesskf", event.x))
sub_menu.append(bright_item)
_add_separetor(sub_menu)
del_item = _get_menu_item(_("Delete"), callback, (clip, track, "delete", event.x))
sub_menu.append(del_item)
lift_item = _get_menu_item(_("Lift"), callback, (clip, track, "lift", event.x))
sub_menu.append(lift_item)
_add_separetor(sub_menu)
length_item = _get_menu_item(_("Set Clip Length..."), callback, (clip, track, "length", event.x))
sub_menu.append(length_item)
stretch_next_item = _get_menu_item(_("Stretch Over Next Blank"), callback, (clip, track, "stretch_next", event.x))
sub_menu.append(stretch_next_item)
stretch_prev_item = _get_menu_item(_("Stretch Over Prev Blank"), callback, (clip, track, "stretch_prev", event.x))
sub_menu.append(stretch_prev_item)
menu_item.show()
return menu_item
def _get_clone_filters_menu_item(event, clip, track, callback):
menu_item = Gtk.MenuItem(_("Clone Filters"))
sub_menu = Gtk.Menu()
menu_item.set_submenu(sub_menu)
clone_item = Gtk.MenuItem(_("From Next Clip"))
sub_menu.append(clone_item)
clone_item.connect("activate", callback, (clip, track, "clone_filters_from_next", None))
clone_item.show()
clone_item = Gtk.MenuItem(_("From Previous Clip"))
sub_menu.append(clone_item)
clone_item.connect("activate", callback, (clip, track, "clone_filters_from_prev", None))
clone_item.show()
menu_item.show()
return menu_item
def _get_mute_menu_item(event, clip, track, callback):
menu_item = Gtk.MenuItem(_("Mute"))
sub_menu = Gtk.Menu()
menu_item.set_submenu(sub_menu)
item = Gtk.MenuItem(_("Unmute"))
sub_menu.append(item)
item.connect("activate", callback, (clip, track, "mute_clip", (False)))
item.show()
item.set_sensitive(not(clip.mute_filter==None))
item = Gtk.MenuItem(_("Mute Audio"))
sub_menu.append(item)
item.connect("activate", callback, (clip, track, "mute_clip", (True)))
item.show()
item.set_sensitive(clip.mute_filter==None)
menu_item.show()
return menu_item
def _get_track_mute_menu_item(event, track, callback):
menu_item = Gtk.MenuItem(_("Mute"))
sub_menu = Gtk.Menu()
menu_item.set_submenu(sub_menu)
item = Gtk.MenuItem(_("Unmute"))
sub_menu.append(item)
if track.type == appconsts.VIDEO:
item.connect("activate", callback, (track, "mute_track", appconsts.TRACK_MUTE_NOTHING))
_set_non_sensitive_if_state_matches(track, item, appconsts.TRACK_MUTE_NOTHING)
else:
item.connect("activate", callback, (track, "mute_track", appconsts.TRACK_MUTE_VIDEO))
_set_non_sensitive_if_state_matches(track, item, appconsts.TRACK_MUTE_VIDEO)
item.show()
if track.type == appconsts.VIDEO:
item = Gtk.MenuItem(_("Mute Video"))
sub_menu.append(item)
item.connect("activate", callback, (track, "mute_track", appconsts.TRACK_MUTE_VIDEO))
_set_non_sensitive_if_state_matches(track, item, appconsts.TRACK_MUTE_VIDEO)
item.show()
item = Gtk.MenuItem(_("Mute Audio"))
sub_menu.append(item)
if track.type == appconsts.VIDEO:
item.connect("activate", callback, (track, "mute_track", appconsts.TRACK_MUTE_AUDIO))
_set_non_sensitive_if_state_matches(track, item, appconsts.TRACK_MUTE_AUDIO)
else:
item.connect("activate", callback, (track, "mute_track", appconsts.TRACK_MUTE_ALL))
_set_non_sensitive_if_state_matches(track, item, appconsts.TRACK_MUTE_ALL)
item.show()
if track.type == appconsts.VIDEO:
item = Gtk.MenuItem(_("Mute All"))
sub_menu.append(item)
item.connect("activate", callback, (track, "mute_track", appconsts.TRACK_MUTE_ALL))
_set_non_sensitive_if_state_matches(track, item, appconsts.TRACK_MUTE_ALL)
item.show()
menu_item.show()
return menu_item
def _get_clip_properties_menu_item(event, clip, track, callback):
properties_menu_item = Gtk.MenuItem(_("Properties"))
properties_menu = Gtk.Menu()
properties_menu.add(_get_menu_item(_("Rename Clip"), callback,\
(clip, track, "rename_clip", event.x)))
properties_menu.add(_get_color_menu_item(clip, track, callback))
properties_menu_item.set_submenu(properties_menu)
properties_menu_item.show_all()
return properties_menu_item
def _get_color_menu_item(clip, track, callback):
color_menu_item = Gtk.MenuItem(_("Clip Color"))
color_menu = Gtk.Menu()
color_menu.add(_get_menu_item(_("Default"), callback, (clip, track, "clip_color", "default")))
color_menu.add(_get_menu_item(_("Red"), callback, (clip, track, "clip_color", "red")))
color_menu.add(_get_menu_item(_("Green"), callback, (clip, track, "clip_color", "green")))
color_menu.add(_get_menu_item(_("Blue"), callback, (clip, track, "clip_color", "blue")))
color_menu.add(_get_menu_item(_("Orange"), callback, (clip, track, "clip_color", "orange")))
color_menu.add(_get_menu_item(_("Brown"), callback, (clip, track, "clip_color", "brown")))
color_menu.add(_get_menu_item(_("Olive"), callback, (clip, track, "clip_color", "olive")))
color_menu_item.set_submenu(color_menu)
color_menu_item.show_all()
return color_menu_item
def _get_clip_markers_menu_item(event, clip, track, callback):
markers_menu_item = Gtk.MenuItem(_("Markers"))
markers_menu = Gtk.Menu()
markers_exist = len(clip.markers) != 0
#menu = markers_menu
#guiutils.remove_children(menu)
if markers_exist:
for i in range(0, len(clip.markers)):
marker = clip.markers[i]
name, frame = marker
item_str = utils.get_tc_string(frame) + " " + name
markers_menu.add(_get_menu_item(item_str, callback, (clip, track, "go_to_clip_marker", str(i))))
_add_separetor(markers_menu)
else:
no_markers_item = _get_menu_item(_("No Clip Markers"), callback, "dummy", False)
markers_menu.add(no_markers_item)
_add_separetor(markers_menu)
markers_menu.add(_get_menu_item(_("Add Clip Marker At Playhead Position"), callback, (clip, track, "add_clip_marker", None)))
del_item = _get_menu_item(_("Delete Clip Marker At Playhead Position"), callback, (clip, track, "delete_clip_marker", None), markers_exist==True)
markers_menu.add(del_item)
del_all_item = _get_menu_item(_("Delete All Clip Markers"), callback, (clip, track, "deleteall_clip_markers", None), markers_exist==True)
markers_menu.add(del_all_item)
markers_menu_item.set_submenu(markers_menu)
markers_menu_item.show_all()
return markers_menu_item
def _set_non_sensitive_if_state_matches(mutable, item, state):
if mutable.mute_state == state:
item.set_sensitive(False)
def display_media_file_popup_menu(media_file, callback, event):
media_file_menu = media_file_popup_menu
guiutils.remove_children(media_file_menu)
# "Open in Clip Monitor" is sent as event id, same for all below
media_file_menu.add(_get_menu_item(_("Rename"), callback,("Rename", media_file, event)))
media_file_menu.add(_get_menu_item(_("Delete"), callback,("Delete", media_file, event)))
_add_separetor(media_file_menu)
media_file_menu.add(_get_menu_item(_("Open in Clip Monitor"), callback,("Open in Clip Monitor", media_file, event)))
if media_file.type != appconsts.PATTERN_PRODUCER:
media_file_menu.add(_get_menu_item(_("File Properties"), callback, ("File Properties", media_file, event)))
if media_file.type != appconsts.IMAGE and media_file.type != appconsts.AUDIO and media_file.type != appconsts.PATTERN_PRODUCER:
_add_separetor(media_file_menu)
if media_file.type != appconsts.IMAGE_SEQUENCE:
media_file_menu.add(_get_menu_item(_("Render Slow/Fast Motion File"), callback, ("Render Slow/Fast Motion File", media_file, event)))
if media_file.type != appconsts.IMAGE_SEQUENCE:
media_file_menu.add(_get_menu_item(_("Render Reverse Motion File"), callback, ("Render Reverse Motion File", media_file, event)))
if media_file.type == appconsts.VIDEO or media_file.type == appconsts.IMAGE_SEQUENCE:
item = _get_menu_item(_("Render Proxy File"), callback, ("Render Proxy File", media_file, event))
media_file_menu.add(item)
media_file_menu.popup(None, None, None, None, event.button, event.time)
def display_filter_stack_popup_menu(row, treeview, callback, event):
filter_stack_menu = filter_stack_menu_popup_menu
guiutils.remove_children(filter_stack_menu)
filter_stack_menu.add(_get_menu_item(_("Toggle Active"), callback, ("toggle", row, treeview)))
filter_stack_menu.add(_get_menu_item(_("Reset Values"), callback, ("reset", row, treeview)))
_add_separetor(filter_stack_menu)
filter_stack_menu.add(_get_menu_item(_("Move Up"), callback, ("moveup", row, treeview)))
filter_stack_menu.add(_get_menu_item(_("Move Down"), callback, ("movedown", row, treeview)))
filter_stack_menu.popup(None, None, None, None, event.button, event.time)
def display_media_log_event_popup_menu(row, treeview, callback, event):
log_event_menu = log_event_popup_menu
guiutils.remove_children(log_event_menu)
log_event_menu.add(_get_menu_item(_("Display In Clip Monitor"), callback, ("display", row, treeview)))
log_event_menu.add(_get_menu_item(_("Render Slow/Fast Motion File"), callback, ("renderslowmo", row, treeview)))
log_event_menu.add(_get_menu_item(_("Toggle Star"), callback, ("toggle", row, treeview)))
log_event_menu.add(_get_menu_item(_("Delete"), callback, ("delete", row, treeview)))
log_event_menu.popup(None, None, None, None, event.button, event.time)
def display_media_linker_popup_menu(row, treeview, callback, event):
media_linker_menu = media_linker_popup_menu
guiutils.remove_children(media_linker_menu)
media_linker_menu.add(_get_menu_item(_("Set File Relink Path"), callback, ("set relink", row)))
media_linker_menu.add(_get_menu_item(_("Delete File Relink Path"), callback, ("delete relink", row)))
_add_separetor(media_linker_menu)
media_linker_menu.add(_get_menu_item(_("Show Full Paths"), callback, ("show path", row)))
media_linker_menu.popup(None, None, None, None, event.button, event.time)
def _add_separetor(menu):
sep = Gtk.SeparatorMenuItem()
sep.show()
menu.add(sep)
def _get_menu_item(text, callback, data, sensitive=True):
item = Gtk.MenuItem.new_with_label(text)
item.connect("activate", callback, data)
item.show()
item.set_sensitive(sensitive)
return item
def _get_radio_menu_item(text, callback, group):
item = Gtk.RadioMenuItem(group, text, False)
item.show()
return item
def _get_image_menu_item(img, text, callback, data):
item = Gtk.ImageMenuItem()
item.set_image(img)
item.connect("activate", callback, data)
item.set_always_show_image(True)
item.set_use_stock(False)
item.set_label(text)
item.show()
return item
# --------------------------------------------------- profile info gui
def get_profile_info_box(profile, show_description=True):
# Labels text
label_label = Gtk.Label()
set_profile_info_labels_text(label_label, show_description)
# Values text
value_label = Gtk.Label()
set_profile_info_values_text(profile, value_label, show_description)
# Create box
hbox = Gtk.HBox()
hbox.pack_start(label_label, False, False, 0)
hbox.pack_start(value_label, True, True, 0)
return hbox
def get_profile_info_small_box(profile):
text = get_profile_info_text(profile)
label = Gtk.Label(label=text)
hbox = Gtk.HBox()
hbox.pack_start(label, False, False, 0)
return hbox
def get_profile_info_text(profile):
str_list = []
str_list.append(str(profile.width()))
str_list.append(" x ")
str_list.append(str(profile.height()))
str_list.append(", " + str(profile.display_aspect_num()))
str_list.append(":")
str_list.append(str(profile.display_aspect_den()))
str_list.append(", ")
if profile.progressive() == True:
str_list.append(_("Progressive"))
else:
str_list.append(_("Interlaced"))
str_list.append("\n")
fps_str = utils.get_fps_str_with_two_decimals(str(profile.fps()))
str_list.append(_("Fps: ") + fps_str)
pix_asp = float(profile.sample_aspect_num()) / profile.sample_aspect_den()
pa_str = "%.2f" % pix_asp
str_list.append(", " + _("Pixel Aspect: ") + pa_str)
return ''.join(str_list)
def set_profile_info_labels_text(label, show_description):
str_list = []
if show_description:
str_list.append(_("Description:"))
str_list.append("\n")
str_list.append(_("Dimensions:"))
str_list.append("\n")
str_list.append(_("Frames per second:"))
str_list.append("\n")
str_list.append(_("Size:"))
str_list.append("\n")
str_list.append(_("Pixel aspect ratio: "))
str_list.append("\n")
str_list.append(_("Progressive:"))
label_label_text = ''.join(str_list)
label.set_text(label_label_text)
label.set_justify(Gtk.Justification.LEFT)
def set_profile_info_values_text(profile, label, show_description):
str_list = []
# round fractional frame rates to something easier to read
fps = profile.fps()
display_fps = str(round(fps))
if 0 != (fps % 1):
display_fps = str(round((float(fps)), 2))
if show_description:
str_list.append(profile.description())
str_list.append("\n")
str_list.append(str(profile.display_aspect_num()))
str_list.append(":")
str_list.append(str(profile.display_aspect_den()))
str_list.append("\n")
str_list.append(display_fps)
str_list.append("\n")
str_list.append(str(profile.width()))
str_list.append(" x ")
str_list.append(str(profile.height()))
str_list.append("\n")
pix_asp = float(profile.sample_aspect_num()) / profile.sample_aspect_den()
pa_str = "%.2f" % pix_asp
str_list.append(pa_str)
str_list.append("\n")
if profile.progressive() == True:
prog = _("Yes")
else:
prog = _("No")
str_list.append(prog)
value_label_text = ''.join(str_list)
label.set_text(value_label_text)
label.set_justify(Gtk.Justification.LEFT)
class BigTCDisplay:
def __init__(self):
# Aug-2019 - SvdB -BB
prefs = editorpersistance.prefs
size_adj = 1
if prefs.double_track_hights:
size_adj = 2
self.widget = cairoarea.CairoDrawableArea2( 170*size_adj,
22*size_adj,
self._draw)
self.font_desc = Pango.FontDescription("Bitstream Vera Sans Mono Condensed "+str(15*size_adj))
# Draw consts
x = 2
y = 2
width = 166*size_adj
height = 24*size_adj
aspect = 1.0
corner_radius = height / 3.5
radius = corner_radius / aspect
degrees = M_PI / 180.0
self._draw_consts = (x, y, width, height, aspect, corner_radius, radius, degrees)
self.TEXT_X = 18
self.TEXT_Y = 1
self.widget.connect("button-press-event", self._button_press)
def _draw(self, event, cr, allocation):
"""
Callback for repaint from CairoDrawableArea.
We get cairo contect and allocation.
"""
x, y, w, h = allocation
# Draw round rect with gradient and stroke around for thin bezel
self._round_rect_path(cr)
if editorpersistance.prefs.theme == appconsts.LIGHT_THEME:
cr.set_source_rgb(0.2, 0.2, 0.2)
else:
cr.set_source_rgb(0.1, 0.1, 0.1)
cr.fill_preserve()
if editorpersistance.prefs.theme == appconsts.LIGHT_THEME:
grad = cairo.LinearGradient (0, 0, 0, h)
for stop in BIG_TC_GRAD_STOPS:
grad.add_color_stop_rgba(*stop)
cr.set_source(grad)
cr.fill_preserve()
grad = cairo.LinearGradient (0, 0, 0, h)
for stop in BIG_TC_FRAME_GRAD_STOPS:
grad.add_color_stop_rgba(*stop)
cr.set_source(grad)
cr.set_line_width(1)
cr.stroke()
# Get current TIMELINE frame str
try:
frame = PLAYER().tracktor_producer.frame()
frame_str = utils.get_tc_string(frame)
except:
frame_str = "00:00:00:00"
# Text
layout = PangoCairo.create_layout(cr)
layout.set_text(frame_str, -1)
layout.set_font_description(self.font_desc)
cr.set_source_rgb(*TC_COLOR)
cr.move_to(self.TEXT_X, self.TEXT_Y)
PangoCairo.update_layout(cr, layout)
PangoCairo.show_layout(cr, layout)
def _round_rect_path(self, cr):
x, y, width, height, aspect, corner_radius, radius, degrees = self._draw_consts
cr.new_sub_path()
cr.arc (x + width - radius, y + radius, radius, -90 * degrees, 0 * degrees)
cr.arc (x + width - radius, y + height - radius, radius, 0 * degrees, 90 * degrees)
cr.arc (x + radius, y + height - radius, radius, 90 * degrees, 180 * degrees)
cr.arc (x + radius, y + radius, radius, 180 * degrees, 270 * degrees)
cr.close_path ()
def _button_press(self, widget, event):
gui.big_tc.set_visible_child_name("BigTCEntry")
entry = gui.big_tc.get_visible_child()
entry.set_text(BigTCEntry().get_current_frame_text())
entry.grab_focus()
def _seek_frame(self, frame):
PLAYER().seek_frame(frame)
class BigTCEntry:
"""
Test class for replacement of BigTCDisplay, when Editing time position
"""
def __init__(self):
self.widget = Gtk.Entry()
frame_str = self.get_current_frame_text()
self.widget.set_text(frame_str)
self.visible = False
self.widget.connect("activate", self._enter_pressed)
self.widget.connect("focus-out-event", self._focus_lost)
def get_current_frame_text(self):
try:
frame = PLAYER().tracktor_producer.frame()
frame_str = utils.get_tc_string(frame)
except:
frame_str = "00:00:00:00"
return frame_str
def _handle_set_time(self):
frame_str = gui.big_tc.get_visible_child().get_text()
frame = utils.get_tc_frame(frame_str)
gui.big_tc.set_visible_child_name("BigTCDisplay")
PLAYER().seek_frame(int(frame))
def _enter_pressed(self, event):
self._handle_set_time()
gui.pos_bar.widget.grab_focus()
def _focus_lost(self, widget, event):
if gui.big_tc.get_visible_child_name() == "BigTCEntry":
self._handle_set_time()
class MonitorTCDisplay:
"""
Mostly copy-pasted from BigTCDisplay, just enough different to make common inheritance
annoying.
"""
def __init__(self):
self.widget = cairoarea.CairoDrawableArea2( 94,
20,
self._draw)
self.font_desc = Pango.FontDescription("Bitstream Vera Sans Mono Condensed 9")
# Draw consts
x = 2
y = 2
width = 90
height = 16
aspect = 1.0
corner_radius = height / 3.5
radius = corner_radius / aspect
degrees = M_PI / 180.0
self._draw_consts = (x, y, width, height, aspect, corner_radius, radius, degrees)
self.FPS_NOT_SET = -99.0
self._frame = 0
self.use_internal_frame = False
self.use_internal_fps = False # if False, fps value for calulating tc comes from utils.fps(),
# if True, fps value from self.fps that will have to be set from user site
self.fps = self.FPS_NOT_SET # this will have to be set from user site
def set_frame(self, frame):
self._frame = frame # this is used in tools, editor window uses PLAYER frame
self.widget.queue_draw()
def _draw(self, event, cr, allocation):
"""
Callback for repaint from CairoDrawableArea.
We get cairo contect and allocation.
"""
x, y, w, h = allocation
# Draw round rect with gradient and stroke around for thin bezel
self._round_rect_path(cr)
if editorpersistance.prefs.theme == appconsts.LIGHT_THEME:
cr.set_source_rgb(0.2, 0.2, 0.2)
else:
cr.set_source_rgb(0.1, 0.1, 0.1)
cr.fill_preserve()
if editorpersistance.prefs.theme == appconsts.LIGHT_THEME:
grad = cairo.LinearGradient (0, 0, 0, h)
for stop in BIG_TC_GRAD_STOPS:
grad.add_color_stop_rgba(*stop)
cr.set_source(grad)
cr.fill_preserve()
grad = cairo.LinearGradient (0, 0, 0, h)
for stop in BIG_TC_FRAME_GRAD_STOPS:
grad.add_color_stop_rgba(*stop)
cr.set_source(grad)
cr.set_line_width(1)
cr.stroke()
# Get current TIMELINE frame str
if self.use_internal_frame:
frame = self._frame
else:
frame = PLAYER().tracktor_producer.frame() # is this used actually?
if self.use_internal_fps == False:
frame_str = utils.get_tc_string(frame)
else:
if self.fps != self.FPS_NOT_SET:
frame_str = utils.get_tc_string_with_fps(frame, self.fps)
else:
frame_str = ""
# Text
layout = PangoCairo.create_layout(cr)
layout.set_text(frame_str, -1)
layout.set_font_description(self.font_desc)
cr.set_source_rgb(0.7, 0.7, 0.7)
cr.move_to(8, 2)
PangoCairo.update_layout(cr, layout)
PangoCairo.show_layout(cr, layout)
def _round_rect_path(self, cr):
x, y, width, height, aspect, corner_radius, radius, degrees = self._draw_consts
cr.new_sub_path()
cr.arc (x + width - radius, y + radius, radius, -90 * degrees, 0 * degrees)
cr.arc (x + width - radius, y + height - radius, radius, 0 * degrees, 90 * degrees)
cr.arc (x + radius, y + height - radius, radius, 90 * degrees, 180 * degrees)
cr.arc (x + radius, y + radius, radius, 180 * degrees, 270 * degrees)
cr.close_path ()
class MonitorTCInfo:
def __init__(self):
if editorstate.screen_size_small_height() == True:
font_desc = "sans bold 8"
else:
font_desc = "sans bold 9"
self.widget = Gtk.HBox()
self.widget.set_tooltip_text(_("Current Sequence / Clip name and length"))
self.monitor_source = Gtk.Label()
self.monitor_source.modify_font(Pango.FontDescription(font_desc))
self.monitor_source.set_ellipsize(Pango.EllipsizeMode.END)
self.monitor_source.set_sensitive(False)
self.monitor_tc = Gtk.Label()
self.monitor_tc.modify_font(Pango.FontDescription(font_desc))
self.in_label = Gtk.Label(label="] ")
self.in_label.modify_font(Pango.FontDescription(font_desc))
self.in_label.set_sensitive(False)
self.out_label = Gtk.Label(label="[ ")
self.out_label.modify_font(Pango.FontDescription(font_desc))
self.out_label.set_sensitive(False)
self.marks_length_label = Gtk.Label(label="][ ")
self.marks_length_label.modify_font(Pango.FontDescription(font_desc))
self.marks_length_label.set_sensitive(False)
self.in_value = Gtk.Label(label="--:--:--:--")
self.in_value.modify_font(Pango.FontDescription(font_desc))
self.out_value = Gtk.Label(label="--:--:--:--")
self.out_value.modify_font(Pango.FontDescription(font_desc))
self.marks_length_value = Gtk.Label(label="--:--:--:--")
self.marks_length_value.modify_font(Pango.FontDescription(font_desc))
self.widget.pack_start(self.monitor_source, False, False, 0)
self.widget.pack_start(self.monitor_tc, False, False, 0)
self.widget.pack_start(guiutils.pad_label(24, 10), False, False, 0)
if editorstate.screen_size_small_width() == False:
self.widget.pack_start(self.in_label, False, False, 0)
self.widget.pack_start(self.in_value, False, False, 0)
self.widget.pack_start(guiutils.pad_label(12, 10), False, False, 0)
self.widget.pack_start(self.out_label, False, False, 0)
self.widget.pack_start(self.out_value, False, False, 0)
self.widget.pack_start(guiutils.pad_label(12, 10), False, False, 0)
self.widget.pack_start(self.marks_length_label, False, False, 0)
self.widget.pack_start(self.marks_length_value, False, False, 0)
def set_source_name(self, source_name):
self.monitor_source.set_text(source_name)
def set_source_tc(self, tc_str):
self.monitor_tc.set_text(tc_str)
def set_range_info(self, in_str, out_str, len_str):
if editorstate.screen_size_small_width() == False:
self.in_value.set_text(in_str)
self.out_value.set_text(out_str)
self.marks_length_value.set_text(len_str)
class TimeLineLeftBottom:
def __init__(self):
self.widget = Gtk.HBox()
self.update_gui()
def update_gui(self):
for child in self.widget.get_children():
self.widget.remove(child)
self.widget.pack_start(Gtk.Label(), True, True, 0)
if PROJECT().proxy_data.proxy_mode == appconsts.USE_PROXY_MEDIA:
proxy_img = Gtk.Image.new_from_file(respaths.IMAGE_PATH + "project_proxy.png")
self.widget.pack_start(proxy_img, False, False, 0)
self.widget.pack_start(guiutils.pad_label(4,4), False, False, 0)
self.widget.show_all()
self.widget.queue_draw()
class TracksNumbersSelect:
def __init__(self, v_tracks, a_tracks):
self.MAX_TRACKS = appconsts.MAX_TRACKS
self.widget = Gtk.HBox()
self.video_label = Gtk.Label(_("Video:"))
self.video_tracks = Gtk.SpinButton.new_with_range(1, self.MAX_TRACKS, 1)
self.video_tracks.set_value(v_tracks)
self.video_tracks.connect("value-changed", self.video_tracks_changed)
self.audio_label = Gtk.Label(_("Audio:"))
self.audio_tracks = Gtk.SpinButton.new_with_range(0, self.MAX_TRACKS-1, 1)
self.audio_tracks.set_value(a_tracks)
self.audio_tracks.connect("value-changed", self.audio_tracks_changed)
self.label = Gtk.Label(_("Number of Tracks:"))
self.tracks_amount_info = Gtk.Label()
self.set_total_tracks_info()
self.widget.pack_start(self.label, False, False, 0)
self.widget.pack_start(guiutils.pad_label(22,2), False, False, 0)
self.widget.pack_start(self.video_label, False, False, 0)
self.widget.pack_start(self.video_tracks, False, False, 0)
self.widget.pack_start(guiutils.pad_label(22,2), False, False, 0)
self.widget.pack_start(self.audio_label, False, False, 0)
self.widget.pack_start(self.audio_tracks, False, False, 0)
self.widget.pack_start(guiutils.pad_label(22,2), False, False, 0)
self.widget.pack_start(self.tracks_amount_info, False, False, 0)
self.widget.pack_start(Gtk.Label(), True, True, 0)
def video_tracks_changed(self, adjustment):
if self.video_tracks.get_value() + self.audio_tracks.get_value() > self.MAX_TRACKS:
self.audio_tracks.set_value(self.MAX_TRACKS - self.video_tracks.get_value())
self.set_total_tracks_info()
def audio_tracks_changed(self, adjustment):
if self.video_tracks.get_value() + self.audio_tracks.get_value() > self.MAX_TRACKS:
self.video_tracks.set_value(self.MAX_TRACKS - self.audio_tracks.get_value())
self.set_total_tracks_info()
def set_total_tracks_info(self):
self.tracks_amount_info.set_text(str(int(self.video_tracks.get_value() + self.audio_tracks.get_value())) + " / " + str(self.MAX_TRACKS))
self.tracks_amount_info.queue_draw ()
def get_tracks(self):
return (int(self.video_tracks.get_value()), int(self.audio_tracks.get_value()))
class ClipLengthChanger:
def __init__(self, clip):
self.clip = clip
self.widget = Gtk.HBox()
frames = clip.clip_length()
self.max_len = clip.get_length()
self.frames_label = Gtk.Label(_("Frames:"))
self.frames_spin = Gtk.SpinButton.new_with_range(1, self.max_len, 1)
self.frames_spin.set_value(frames)
self.frames_spin.connect("value-changed", self._length_changed)
self.tc_length = Gtk.Entry()
self.tc_length.set_text(utils.get_tc_string(frames))
self.tc_length.connect("activate", self._enter_pressed)
self.widget.pack_start(self.frames_label, False, False, 0)
self.widget.pack_start(self.frames_spin, False, False, 0)
self.widget.pack_start(guiutils.pad_label(22,2), False, False, 0)
self.widget.pack_start(self.tc_length, False, False, 0)
self.widget.pack_start(Gtk.Label(), True, True, 0)
def _length_changed(self, adjustment):
self.tc_length.set_text(utils.get_tc_string(self.frames_spin.get_value()))
def _enter_pressed(self, event):
frame_str = self.tc_length.get_text()
frame = utils.get_tc_frame(frame_str)
if frame > self.max_len:
frame = self.max_len
self.tc_length.set_text(utils.get_tc_string(frame))
if frame < 0:
frame = 0
self.tc_length.set_text(utils.get_tc_string(frame))
self.frames_spin.set_value(frame)
def get_length(self):
return int(self.frames_spin.get_value())
def get_gpl3_scroll_widget(size):
license_file = open(respaths.GPL_3_DOC)
license_text = license_file.read()
view = Gtk.TextView()
view.set_editable(False)
view.set_pixels_above_lines(2)
view.set_left_margin(2)
view.set_wrap_mode(Gtk.WrapMode.WORD)
view.get_buffer().set_text(license_text)
sw = Gtk.ScrolledWindow()
sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
sw.add(view)
sw.set_size_request(*size)
return sw
def get_translations_scroll_widget(size):
trans_file = open(respaths.TRANSLATIONS_DOC)
trans_text = trans_file.read()
return get_text_scroll_widget(trans_text, size)
def get_text_scroll_widget(text, size):
view = Gtk.TextView()
view.set_editable(False)
view.set_pixels_above_lines(2)
view.set_left_margin(2)
view.set_wrap_mode(Gtk.WrapMode.WORD)
view.get_buffer().set_text(text)
sw = Gtk.ScrolledWindow()
sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
sw.add(view)
sw.set_size_request(*size)
return sw
# Aug-2019 - SvdB - BB - Need to add w/h
def get_markers_menu_launcher(callback, pixbuf, w=22, h=22):
m_launch = PressLaunch(callback, pixbuf, w, h)
return m_launch
def get_markers_popup_menu(event, callback):
seq = current_sequence()
markers_exist = len(seq.markers) != 0
menu = markers_menu
guiutils.remove_children(menu)
if markers_exist:
for i in range(0, len(seq.markers)):
marker = seq.markers[i]
name, frame = marker
item_str = utils.get_tc_string(frame) + " " + name
menu.add(_get_menu_item(item_str, callback, str(i) ))
_add_separetor(menu)
else:
no_markers_item = _get_menu_item(_("No Markers"), callback, "dummy", False)
menu.add(no_markers_item)
_add_separetor(menu)
menu.add(_get_menu_item(_("Add Marker"), callback, "add" ))
del_item = _get_menu_item(_("Delete Marker"), callback, "delete", markers_exist==True)
menu.add(del_item)
del_all_item = _get_menu_item(_("Delete All Markers"), callback, "deleteall", markers_exist==True)
menu.add(del_all_item)
menu.show_all()
menu.popup(None, None, None, None, event.button, event.time)
def get_all_tracks_popup_menu(event, callback):
menu = tracks_menu
guiutils.remove_children(menu)
menu.add(_get_menu_item(_("Maximize Tracks"), callback, "max" ))
menu.add(_get_menu_item(_("Maximize Video Tracks"), callback, "maxvideo" ))
menu.add(_get_menu_item(_("Maximize Audio Tracks"), callback, "maxaudio" ))
_add_separetor(menu)
menu.add(_get_menu_item(_("Minimize Tracks"), callback, "min" ))
_add_separetor(menu)
menu.add(_get_menu_item(_("Activate All Tracks"), callback, "allactive" ))
menu.add(_get_menu_item(_("Activate Only Current Top Active Track"), callback, "topactiveonly" ))
_add_separetor(menu)
shrink_tline_item = Gtk.CheckMenuItem(_("Vertical Shrink Timeline"))
shrink_tline_item.set_active(PROJECT().get_project_property(appconsts.P_PROP_TLINE_SHRINK_VERTICAL))
shrink_tline_item.show()
shrink_tline_item.connect("toggled", callback, "shrink" )
if len(current_sequence().tracks) == 11:
shrink_tline_item.set_sensitive(False) # This can't do anything if 9 editable tracks in sequence
menu.append(shrink_tline_item)
menu.popup(None, None, None, None, event.button, event.time)
def get_audio_levels_popup_menu(event, callback):
# needs renaming, we have more stuff here now
menu = levels_menu
guiutils.remove_children(menu)
thumbs_item = Gtk.CheckMenuItem()
thumbs_item.set_label(_("Display Clip Media Thumbnails"))
thumbs_item.set_active(editorstate.display_clip_media_thumbnails)
thumbs_item.connect("activate", callback, "thumbs")
menu.append(thumbs_item)
_add_separetor(menu)
snapping_item = Gtk.CheckMenuItem()
snapping_item.set_label(_("Snapping On"))
snapping_item.set_active(snapping.snapping_on)
snapping_item.connect("activate", callback, "snapping")
menu.append(snapping_item)
_add_separetor(menu)
scrub_item = Gtk.CheckMenuItem()
scrub_item.set_label(_("Audio scrubbing"))
scrub_item.set_active(editorpersistance.prefs.audio_scrubbing)
scrub_item.connect("activate", callback, "scrubbing")
menu.append(scrub_item)
_add_separetor(menu)
allways_item = Gtk.RadioMenuItem()
allways_item.set_label(_("Display All Audio Levels"))
menu.append(allways_item)
on_request_item = Gtk.RadioMenuItem.new_with_label([allways_item], _("Display Audio Levels On Request"))
menu.append(on_request_item)
if editorstate.display_all_audio_levels == True:
on_request_item.connect("activate", callback, "on request")
allways_item.set_active(True)
on_request_item.set_active(False)
else:
allways_item.connect("activate", callback, "all")
allways_item.set_active(False)
on_request_item.set_active(True)
menu.show_all()
menu.popup(None, None, None, None, event.button, event.time)
def get_clip_effects_editor_hamburger_menu(event, callback):
# needs renaming
menu = clip_effects_hamburger_menu
guiutils.remove_children(menu)
menu.add(_get_menu_item(_("Save Effect Values"), callback, "save"))
menu.add(_get_menu_item(_("Load Effect Values"), callback, "load"))
menu.add(_get_menu_item(_("Reset Effect Values"), callback, "reset"))
_add_separetor(menu)
menu.add(_get_menu_item(_("Delete Effect"), callback, "delete"))
_add_separetor(menu)
menu.add(_get_menu_item(_("Close Editor"), callback, "close"))
menu.show_all()
menu.popup(None, None, None, None, event.button, event.time)
def get_compositor_editor_hamburger_menu(event, callback):
# needs renaming
menu = clip_effects_hamburger_menu
guiutils.remove_children(menu)
menu.add(_get_menu_item(_("Save Compositor Values"), callback, "save"))
menu.add(_get_menu_item(_("Load Compositor Values"), callback, "load"))
menu.add(_get_menu_item(_("Reset Compositor Values"), callback, "reset"))
_add_separetor(menu)
menu.add(_get_menu_item(_("Delete Compositor"), callback, "delete"))
_add_separetor(menu)
menu.add(_get_menu_item(_("Close Editor"), callback, "close"))
menu.show_all()
menu.popup(None, None, None, None, event.button, event.time)
def get_monitor_view_popupmenu(launcher, event, callback):
menu = monitor_menu
guiutils.remove_children(menu)
menu.add(_get_image_menu_item(Gtk.Image.new_from_file(
respaths.IMAGE_PATH + "program_view_2.png"), _("Image"), callback, 0))
menu.add(_get_image_menu_item(Gtk.Image.new_from_file(
respaths.IMAGE_PATH + "vectorscope.png"), _("Vectorscope"), callback, 1))
menu.add(_get_image_menu_item(Gtk.Image.new_from_file(
respaths.IMAGE_PATH + "rgbparade.png"), _("RGB Parade"), callback, 2))
_add_separetor(menu)
overlay_menu_item = Gtk.MenuItem(_("Overlay Opacity"))
overlay_menu_item.show()
overlay_menu = Gtk.Menu()
op_100 = Gtk.RadioMenuItem()
op_100.set_label(_("100%"))
op_100.connect("activate", callback, 3)
op_100.show()
overlay_menu.append(op_100)
op_80 = Gtk.RadioMenuItem.new_with_label([op_100], _("80%"))
op_80.connect("activate", callback, 4)
op_80.show()
overlay_menu.append(op_80)
op_50 = Gtk.RadioMenuItem.new_with_label([op_100], _("50%"))
op_50.connect("activate", callback, 5)
op_50.show()
overlay_menu.append(op_50)
op_20 = Gtk.RadioMenuItem.new_with_label([op_100], _("20%"))
op_20.connect("activate", callback, 6)
op_20.show()
overlay_menu.append(op_20)
op_0 = Gtk.RadioMenuItem.new_with_label([op_100], _("0%"))
op_0.connect("activate", callback, 7)
op_0.show()
overlay_menu.append(op_0)
active_index = current_sequence().get_mix_index()
items = [op_100, op_80, op_50, op_20, op_0]
active_item = items[active_index]
active_item.set_active(True)
overlay_menu_item.set_submenu(overlay_menu)
menu.append(overlay_menu_item)
menu.popup(None, None, None, None, event.button, event.time)
def get_trim_view_popupmenu(launcher, event, callback):
menu = trim_view_menu
guiutils.remove_children(menu)
trim_view_all = Gtk.RadioMenuItem()
trim_view_all.set_label(_("Trim View On"))
trim_view_all.show()
menu.append(trim_view_all)
trim_view_single = Gtk.RadioMenuItem.new_with_label([trim_view_all], _("Trim View Single Side Edits Only"))
trim_view_single.show()
menu.append(trim_view_single)
no_trim_view = Gtk.RadioMenuItem.new_with_label([trim_view_all], _("Trim View Off"))
no_trim_view.show()
menu.append(no_trim_view)
active_index = editorstate.show_trim_view # The values for this as defines in appconsts.py correspond to indexes here
items = [trim_view_all, trim_view_single, no_trim_view]
active_item = items[active_index]
active_item.set_active(True)
trim_view_all.connect("activate", callback, "trimon")
trim_view_single.connect("activate", callback, "trimsingle")
no_trim_view.connect("activate", callback, "trimoff")
_add_separetor(menu)
menu_item = _get_menu_item(_("Set Current Clip Frame Match Frame"), callback, "clipframematch" )
if editorstate.timeline_visible() == True:
menu_item.set_sensitive(False)
menu.add(menu_item)
menu_item = _get_menu_item(_("Clear Match Frame"), callback, "matchclear" )
if gui.monitor_widget.view != monitorwidget.FRAME_MATCH_VIEW:
menu_item.set_sensitive(False)
menu.add(menu_item)
menu.popup(None, None, None, None, event.button, event.time)
def get_file_filter_popup_menu(launcher, event, callback):
menu = file_filter_menu
guiutils.remove_children(menu)
menu.set_accel_group(gui.editor_window.accel_group)
menu_item = _get_image_menu_item(Gtk.Image.new_from_file(
respaths.IMAGE_PATH + "show_all_files.png"), _("All Files"), callback, 0)
menu.add(menu_item)
menu_item = _get_image_menu_item(Gtk.Image.new_from_file(
respaths.IMAGE_PATH + "show_video_files.png"), _("Video Files"), callback, 1)
menu.add(menu_item)
menu_item = _get_image_menu_item(Gtk.Image.new_from_file(
respaths.IMAGE_PATH + "show_audio_files.png"), _("Audio Files"), callback, 2)
menu.add(menu_item)
menu_item = _get_image_menu_item(Gtk.Image.new_from_file(
respaths.IMAGE_PATH + "show_graphics_files.png"), _("Graphics Files"), callback, 3)
menu.add(menu_item)
menu_item = _get_image_menu_item(Gtk.Image.new_from_file(
respaths.IMAGE_PATH + "show_imgseq_files.png"), _("Image Sequences"), callback, 4)
menu.add(menu_item)
menu_item = _get_image_menu_item(Gtk.Image.new_from_file(
respaths.IMAGE_PATH + "show_pattern_producers.png"), _("Pattern Producers"), callback, 5)
menu.add(menu_item)
menu.show_all()
menu.popup(None, None, None, None, event.button, event.time)
def get_columns_count_popup_menu(event, callback):
menu = column_count_menu
guiutils.remove_children(menu)
menu.set_accel_group(gui.editor_window.accel_group)
columns = gui.editor_window.media_list_view.columns
menu_item_2 = Gtk.RadioMenuItem()
menu_item_2.set_label(_("2 Columns"))
menu_item_2.set_active(columns==2)
menu_item_2.connect("activate", callback, 2)
menu.append(menu_item_2)
menu_item_3 = Gtk.RadioMenuItem.new_with_label([menu_item_2], _("3 Columns"))
menu_item_3.connect("activate", callback, 3)
menu_item_3.set_active(columns==3)
menu.append(menu_item_3)
menu_item_4 = Gtk.RadioMenuItem.new_with_label([menu_item_2], _("4 Columns"))
menu_item_4.connect("activate", callback, 4)
menu_item_4.set_active(columns==4)
menu.append(menu_item_4)
menu_item_5 = Gtk.RadioMenuItem.new_with_label([menu_item_2], _("5 Columns"))
menu_item_5.connect("activate", callback, 5)
menu_item_5.set_active(columns==5)
menu.append(menu_item_5)
menu_item_6 = Gtk.RadioMenuItem.new_with_label([menu_item_2], _("6 Columns"))
menu_item_6.connect("activate", callback, 6)
menu_item_6.set_active(columns==6)
menu.append(menu_item_6)
menu_item_7 = Gtk.RadioMenuItem.new_with_label([menu_item_2], _("7 Columns"))
menu_item_7.connect("activate", callback, 7)
menu_item_7.set_active(columns==7)
menu.append(menu_item_7)
menu.show_all()
menu.popup(None, None, None, None, event.button, event.time)
def get_shorcuts_selector():
shortcuts_combo = Gtk.ComboBoxText()
current_pref_index = -1
for i in range(0, len(shortcuts.shortcut_files)):
shortcut_file = shortcuts.shortcut_files[i]
shortcuts_combo.append_text(shortcuts.shortcut_files_display_names[i])
if editorpersistance.prefs.shortcuts == shortcut_file:
current_pref_index = i
# Set current selection active
if current_pref_index != -1:
shortcuts_combo.set_active(current_pref_index)
else:
# Something is wrong, the pref shortcut file is not preset in the system.
print("Shortcut file in editprpersistance.pref.shortcuts not found!")
shortcuts_combo.set_active(0)
return shortcuts_combo
class PressLaunch:
def __init__(self, callback, surface, w=22, h=22):
self.widget = cairoarea.CairoDrawableArea2( w,
h,
self._draw)
self.widget.press_func = self._press_event
self.callback = callback
self.surface = surface
self.surface_x = 6
self.surface_y = 6
def _draw(self, event, cr, allocation):
cr.set_source_surface(self.surface, self.surface_x, self.surface_y)
cr.paint()
def _press_event(self, event):
self.callback(self.widget, event)
class ImageMenuLaunch(PressLaunch):
def __init__(self, callback, surface_list, w=22, h=22):
PressLaunch.__init__(self, callback, surface_list[0], w, h)
self.surface_list = surface_list
def set_pixbuf(self, surface_index):
self.surface = self.surface_list[surface_index]
self.widget.queue_draw()
class ToolSelector(ImageMenuLaunch):
def __init__(self, callback, surface_list, w, h):
ImageMenuLaunch.__init__(self, callback, surface_list, w, h)
# surface_list indexes and tool ids need hardcoded mapping, tool_ids and surface indexes cannot easily be made to correspond
self.TOOL_ID_TO_SURFACE_INDEX = { appconsts.TLINE_TOOL_INSERT: 0,
appconsts.TLINE_TOOL_OVERWRITE: 1,
appconsts.TLINE_TOOL_TRIM: 2,
appconsts.TLINE_TOOL_ROLL: 4,
appconsts.TLINE_TOOL_SLIP: 5,
appconsts.TLINE_TOOL_SPACER: 6,
appconsts.TLINE_TOOL_BOX: 7,
appconsts.TLINE_TOOL_RIPPLE_TRIM: 3,
appconsts.TLINE_TOOL_CUT: 8,
appconsts.TLINE_TOOL_KFTOOL: 9,
appconsts.TLINE_TOOL_MULTI_TRIM: 10
}
def set_tool_pixbuf(self, tool_id):
surface_index = self.TOOL_ID_TO_SURFACE_INDEX[tool_id]
self.set_pixbuf(surface_index)
def _draw(self, event, cr, allocation):
PressLaunch._draw(self, event, cr, allocation)
# Aug-2019 - SvdB - BB - If we have larger icons we need to move this a bit and make it a tad larger.
if editorpersistance.prefs.double_track_hights:
x_pos = [40,45,50]
y_pos = [10,20,10]
else:
x_pos = [27,32,37]
y_pos = [13,18,13]
cr.move_to(x_pos[0], y_pos[0])
cr.line_to(x_pos[1], y_pos[1])
cr.line_to(x_pos[2], y_pos[2])
cr.close_path()
if editorpersistance.prefs.theme == appconsts.LIGHT_THEME:
cr.set_source_rgb(0, 0, 0)
else:
cr.set_source_rgb(0.66, 0.66, 0.66)
cr.fill()
class HamburgerPressLaunch:
def __init__(self, callback):
# Aug-2019 - SvdB - BB
prefs = editorpersistance.prefs
size_adj = 1
y_adj = 0
if prefs.double_track_hights:
size_adj = 2
y_adj = -2
self.widget = cairoarea.CairoDrawableArea2( 18*size_adj,
18*size_adj,
self._draw)
self.widget.press_func = self._press_event
self.sensitive = True
self.callback = callback
self.surface_active = guiutils.get_cairo_image("hamburger")
self.surface_not_active = guiutils.get_cairo_image("hamburger_not_active")
self.surface_x = 0
self.surface_y = y_adj
def set_sensitive(self, sensitive):
self.sensitive = sensitive
self.widget.queue_draw()
def _draw(self, event, cr, allocation):
if self.sensitive == True:
surface = self.surface_active
else:
surface = self.surface_not_active
cr.set_source_surface(surface, self.surface_x, self.surface_y)
cr.paint()
def _press_event(self, event):
if self.sensitive == True:
self.callback(self.widget, event)
class MonitorSwitch:
def __init__(self, callback):
self.WIDTH = 84
self.HEIGHT = 22
# Aug-2019 - SvdB - BB - Set the appropriate values based on button size. Use guiutils functions
prefs = editorpersistance.prefs
if prefs.double_track_hights:
self.WIDTH = self.WIDTH * 2
self.HEIGHT = self.HEIGHT * 2
self.widget = cairoarea.CairoDrawableArea2( self.WIDTH ,
self.HEIGHT,
self._draw)
self.widget.set_tooltip_text(_("Display Timeline / Clip on Monitor"))
self.widget.press_func = self._press_event
self.tline_surface = guiutils.get_cairo_image("timeline_button")
self.tline_active_surface = guiutils.get_cairo_image("timeline_button_active")
self.clip_surface = guiutils.get_cairo_image("clip_button")
self.clip_active_surface = guiutils.get_cairo_image("clip_button_active")
self.callback = callback
self.surface_x = 6
self.surface_y = 6
def _draw(self, event, cr, allocation):
if editorstate.timeline_visible():
tline_draw_surface = self.tline_active_surface
clip_draw_surface = self.clip_surface
else:
tline_draw_surface = self.tline_surface
clip_draw_surface = self.clip_active_surface
# Aug-2019 - SvdB - BB - set default offset
prefs = editorpersistance.prefs
def_off = 10
y_off_tline = 7
y_off_clip = 8
if prefs.double_track_hights:
def_off = def_off * 2
y_off_tline = y_off_tline * 2
y_off_clip = y_off_clip * 2
cr.set_source_surface(tline_draw_surface, def_off, y_off_tline)
cr.paint()
# Aug-2019 - SvdB - BB - Calculate offset for displaying the next button
base_off = tline_draw_surface.get_width()
x_off = clip_draw_surface.get_width()
cr.set_source_surface(clip_draw_surface, def_off + base_off + x_off, y_off_clip)
cr.paint()
def _press_event(self, event):
if event.x < self.WIDTH / 2 and editorstate.timeline_visible() == False:
self.callback(appconsts.MONITOR_TLINE_BUTTON_PRESSED)
elif editorstate.timeline_visible() == True:
self.callback(appconsts.MONITOR_CLIP_BUTTON_PRESSED)
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/guiutils.py 0000664 0000000 0000000 00000033512 13610327166 0025326 0 ustar 00root root 0000000 0000000 """
Flowblade Movie Editor is a nonlinear video editor.
Copyright 2012 Janne Liljeblad.
This file is part of Flowblade Movie Editor .
Flowblade Movie Editor 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.
Flowblade Movie Editor is distributed in the hope that 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 Flowblade Movie Editor. If not, see .
"""
"""
Module contains utility methods for creating GUI objects.
"""
import time
import threading
from gi.repository import Gtk, Gdk
from gi.repository import GdkPixbuf
import appconsts
import cairo # Aug-2019 - SvdB - BB
import editorpersistance # Aug-2019 - SvdB - BB
import respaths
import translations
TWO_COLUMN_BOX_HEIGHT = 20
def bold_label(str):
label = Gtk.Label(label=bold_text(str))
label.set_use_markup(True)
return label
def bold_text(str):
return "" + str + ""
def get_left_justified_box(widgets):
hbox = Gtk.HBox()
for widget in widgets:
hbox.pack_start(widget, False, False, 0)
hbox.pack_start(Gtk.Label(), True, True, 0)
return hbox
def get_right_justified_box(widgets):
hbox = Gtk.HBox()
hbox.pack_start(Gtk.Label(), True, True, 0)
for widget in widgets:
hbox.pack_start(widget, False, False, 0)
return hbox
def get_sides_justified_box(widgets, count_of_widgets_on_the_left=1):
hbox = Gtk.HBox()
wgets_added = 0
for widget in widgets:
hbox.pack_start(widget, False, False, 0)
wgets_added +=1
if wgets_added == count_of_widgets_on_the_left:
hbox.pack_start(Gtk.Label(), True, True, 0)
return hbox
def get_centered_box(widgets):
hbox = Gtk.HBox()
hbox.pack_start(Gtk.Label(), True, True, 0)
for widget in widgets:
hbox.pack_start(widget, False, False, 0)
hbox.pack_start(Gtk.Label(), True, True, 0)
return hbox
def get_vbox(widgets, add_pad_label=True, padding=2):
vbox = Gtk.VBox(False, padding)
for widget in widgets:
vbox.pack_start(widget, False, False, 0)
if add_pad_label:
vbox.pack_start(Gtk.Label(), True, True, 0)
return vbox
def get_single_column_box(widgets):
vbox = Gtk.VBox()
for widget in widgets:
vbox.pack_start(get_left_justified_box([widget]), False, False, 0)
vbox.pack_start(Gtk.Label(), True, True, 0)
return vbox
def get_two_column_box(widget1, widget2, left_width):
hbox = Gtk.HBox()
left_box = get_left_justified_box([widget1])
left_box.set_size_request(left_width, TWO_COLUMN_BOX_HEIGHT)
hbox.pack_start(left_box, False, True, 0)
hbox.pack_start(widget2, True, True, 0)
return hbox
def get_two_column_box_right_pad(widget1, widget2, left_width, right_pad):
left_box = get_left_justified_box([widget1])
left_box.set_size_request(left_width, TWO_COLUMN_BOX_HEIGHT)
right_widget_box = get_left_justified_box([widget2])
pad_label = get_pad_label(right_pad, 5)
right_box = Gtk.HBox()
right_box.pack_start(right_widget_box, True, True, 0)
right_box.pack_start(pad_label, False, False, 0)
hbox = Gtk.HBox()
hbox.pack_start(left_box, False, True, 0)
hbox.pack_start(right_box, True, True, 0)
return hbox
def get_checkbox_row_box(checkbox, widget2):
hbox = Gtk.HBox()
hbox.pack_start(checkbox, False, False, 0)
hbox.pack_start(get_pad_label(4, 1), False, False, 0)
hbox.pack_start(widget2, False, False, 0)
hbox.pack_start(Gtk.Label(), True, True, 0)
return hbox
def get_two_row_box(widget1, widget2):
# widget 1 is left justified
top = get_left_justified_box([widget1])
box = Gtk.VBox(False, 2)
box.pack_start(top, False, False, 4)
box.pack_start(widget2, False, False, 0)
return box
def get_right_expand_box(widget1, widget2, center_pad=False):
hbox = Gtk.HBox()
hbox.pack_start(widget1, False, True, 0)
if center_pad:
hbox.pack_start(pad_label(4,4), False, True, 0)
hbox.pack_start(widget2, True, True, 0)
return hbox
# Aug-2019 - SvdB - BB
def get_image_name(img_name, suffix = ".png", double_height = False):
button_size_text = ""
if double_height:
button_size_text = "@2"
img_name = img_name+button_size_text+suffix
return img_name
# Aug-2019 - SvdB - BB
def get_image(img_name, suffix = ".png", force = None):
# Use parameter force as True or False to force the track height no matter what the preferences setting
if force == None:
force = editorpersistance.prefs.double_track_hights
if force:
img_name = img_name + "@2"
return Gtk.Image.new_from_file(respaths.IMAGE_PATH + img_name + suffix)
# Aug-2019 - SvdB - BB
def get_cairo_image(img_name, suffix = ".png", force = None):
# Use parameter force as True or False to force the track height no matter what the preferences setting
if force == None:
force = editorpersistance.prefs.double_track_hights
if force:
img_name = img_name + "@2"
return cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + img_name + suffix)
# Aug-2019 - SvdB - BB
def get_image_button(img_file_name, width, height):
button = Gtk.Button()
icon = get_image(img_file_name)
size_adj = 1
if editorpersistance.prefs.double_track_hights:
size_adj = 2
button_box = Gtk.HBox()
button_box.pack_start(icon, False, False, 0)
button.add(button_box)
button.set_size_request(width*size_adj, height*size_adj)
return button
def get_pad_label(w, h):
label = Gtk.Label()
label.set_size_request(w, h)
return label
def get_multiplied_color(color, m):
"""
Used to create lighter and darker hues of colors.
"""
return (color[0] * m, color[1] * m, color[2] * m)
def get_slider_row(editable_property, listener, slider_name=None):
adjustment = editable_property.get_input_range_adjustment()
editable_property.adjustment = adjustment # patching in to make available for disconnect
hslider = Gtk.HScale()
hslider.set_adjustment(adjustment)
hslider.set_draw_value(False)
spin = Gtk.SpinButton()
spin.set_numeric(True)
spin.set_adjustment(adjustment)
hbox = Gtk.HBox(False, 4)
hbox.pack_start(hslider, True, True, 0)
hbox.pack_start(spin, False, False, 4)
if slider_name == None:
name = editable_property.get_display_name()
else:
name = slider_name
name = translations.get_param_name(name)
editable_property.value_changed_ID = adjustment.connect("value-changed", listener) # saving ID to make it available for disconnect
# This also needs to be after adjustment is set to not loose exiting value for build dummy value
return (get_two_column_editor_row(name, hbox), hslider)
def get_slider_row_and_spin_widget(editable_property, listener, slider_name=None):
adjustment = editable_property.get_input_range_adjustment()
editable_property.adjustment = adjustment # patching in to make available for disconnect
hslider = Gtk.HScale()
hslider.set_adjustment(adjustment)
hslider.set_draw_value(False)
spin = Gtk.SpinButton()
spin.set_numeric(True)
spin.set_adjustment(adjustment)
hbox = Gtk.HBox(False, 4)
hbox.pack_start(hslider, True, True, 0)
hbox.pack_start(spin, False, False, 4)
if slider_name == None:
name = editable_property.get_display_name()
else:
name = slider_name
name = translations.get_param_name(name)
editable_property.value_changed_ID = adjustment.connect("value-changed", listener) # saving ID to make it available for disconnect
# This also needs to be available after adjustment is set to not lose exiting value for build dummy value
return (get_two_column_editor_row(name, hbox), hslider, spin)
def get_non_property_slider_row(lower, upper, step, value=0, listener=None):
hslider = Gtk.HScale()
hslider.set_draw_value(False)
adjustment = hslider.get_adjustment()
adjustment.set_lower(lower)
adjustment.set_upper(upper)
adjustment.set_step_increment(step)
adjustment.set_value(value)
if listener != None:
adjustment.connect("value-changed", listener) # patching in to make available for disconnect
spin = Gtk.SpinButton()
spin.set_numeric(True)
spin.set_adjustment(adjustment)
hbox = Gtk.HBox(False, 4)
hbox.pack_start(hslider, True, True, 0)
hbox.pack_start(spin, False, False, 4)
return (hbox, hslider)
def get_two_column_editor_row(name, editor_widget):
label = Gtk.Label(label=name + ":")
label_box = Gtk.HBox()
label_box.pack_start(label, False, False, 0)
label_box.pack_start(Gtk.Label(), True, True, 0)
label_box.set_size_request(appconsts.PROPERTY_NAME_WIDTH, appconsts.PROPERTY_ROW_HEIGHT)
hbox = Gtk.HBox(False, 2)
hbox.pack_start(label_box, False, False, 4)
hbox.pack_start(editor_widget, True, True, 0)
return hbox
def get_no_pad_named_frame(name, panel):
return get_named_frame(name, panel, 0, 0, 0)
def get_named_frame_with_vbox(name, widgets, left_padding=12, right_padding=6, right_out_padding=4):
vbox = Gtk.VBox()
for widget in widgets:
vbox.pack_start(widget, False, False, 0)
return get_named_frame(name, vbox, left_padding, right_padding, right_out_padding)
def get_named_frame(name, widget, left_padding=12, right_padding=6, right_out_padding=4, tooltip_txt=None):
"""
Gnome style named panel
"""
if name != None:
label = bold_label(name)
label.set_justify(Gtk.Justification.LEFT)
label_box = Gtk.HBox()
label_box.pack_start(label, False, False, 0)
label_box.pack_start(Gtk.Label(), True, True, 0)
if tooltip_txt != None:
label.set_tooltip_markup(tooltip_txt)
alignment = set_margins(widget, right_padding, 0, left_padding, 0)
frame = Gtk.VBox()
if name != None:
frame.pack_start(label_box, False, False, 0)
frame.pack_start(alignment, True, True, 0)
out_align = set_margins(frame, 4, 4, 0, right_out_padding)
return out_align
def get_in_centering_alignment(widget, xsc=0.0, ysc=0.0):
align = Gtk.HBox(False, 0)
align.pack_start(Gtk.Label(), True, True, 0)
align.pack_start(widget, False, False, 0)
align.pack_start(Gtk.Label(), True, True, 0)
return align
def pad_label(w, h):
pad_label = Gtk.Label()
pad_label.set_size_request(w, h)
return pad_label
def get_sized_button(lable, w, h, clicked_listener=None):
b = Gtk.Button(lable)
if clicked_listener != None:
b.connect("clicked", lambda w,e: clicked_listener())
b.set_size_request(w, h)
return b
def get_render_button():
render_button = Gtk.Button()
render_icon = Gtk.Image.new_from_stock(Gtk.STOCK_MEDIA_RECORD,
Gtk.IconSize.BUTTON)
render_button_box = Gtk.HBox()
render_button_box.pack_start(get_pad_label(10, 10), False, False, 0)
render_button_box.pack_start(render_icon, False, False, 0)
render_button_box.pack_start(get_pad_label(5, 10), False, False, 0)
render_button_box.pack_start(Gtk.Label(label=_("Render")), False, False, 0)
render_button_box.pack_start(get_pad_label(10, 10), False, False, 0)
render_button.add(render_button_box)
return render_button
def get_menu_item(text, callback, data, sensitive=True):
item = Gtk.MenuItem(text)
item.connect("activate", callback, data)
item.show()
item.set_sensitive(sensitive)
return item
def get_radio_menu_items_group(menu, labels, msgs, callback, active_index):
first_item = Gtk.RadioMenuItem()
first_item.set_label(labels[0])
first_item.show()
menu.append(first_item)
if active_index == 0:
first_item.set_active(True)
first_item.connect("activate", callback, msgs[0])
for i in range(1, len(labels)):
radio_item = Gtk.RadioMenuItem.new_with_label([first_item], labels[i])
menu.append(radio_item)
radio_item.show()
if active_index == i:
radio_item.set_active(True)
radio_item.connect("activate", callback, msgs[i])
def add_separetor(menu):
sep = Gtk.SeparatorMenuItem()
sep.show()
menu.add(sep)
def get_gtk_image_from_file(source_path, image_height):
pixbuf = GdkPixbuf.Pixbuf.new_from_file(source_path)
icon_width = int((float(pixbuf.get_width()) / float(pixbuf.get_height())) * image_height)
s_pbuf = pixbuf.scale_simple(icon_width, image_height, GdkPixbuf.InterpType.BILINEAR)
img = Gtk.Image.new_from_pixbuf(s_pbuf)
return img
def set_margins(widget, t, b, l, r):
widget.set_margin_top(t)
widget.set_margin_left(l)
widget.set_margin_bottom(b)
widget.set_margin_right(r)
return widget
def get_theme_bg_color():
return (242.0/255.0, 241.0/ 255.0, 240.0/255.0)
def remove_children(container):
children = container.get_children()
for child in children:
container.remove(child)
class PulseThread(threading.Thread):
def __init__(self, proress_bar):
threading.Thread.__init__(self)
self.proress_bar = proress_bar
def run(self):
self.exited = False
self.running = True
while self.running:
Gdk.threads_enter()
self.proress_bar.pulse()
Gdk.threads_leave()
time.sleep(0.1)
self.exited = True
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/keyevents.py 0000664 0000000 0000000 00000062465 13610327166 0025507 0 ustar 00root root 0000000 0000000 """
Flowblade Movie Editor is a nonlinear video editor.
Copyright 2012 Janne Liljeblad.
This file is part of Flowblade Movie Editor .
Flowblade Movie Editor 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.
Flowblade Movie Editor is distributed in the hope that 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 Flowblade Movie Editor. If not, see .
"""
"""
Module handles keyevents.
"""
from gi.repository import Gdk
import appconsts
import audiowaveform
import clipeffectseditor
import compositeeditor
import compositormodes
import glassbuttons
import gui
import editorpersistance
import editorstate
from editorstate import current_sequence
from editorstate import PLAYER
from editorstate import timeline_visible
import keyframeeditor
import kftoolmode
import medialog
import menuactions
import modesetting
import monitorevent
import movemodes
import multitrimmode
# Apr-2017 - SvdB
import shortcuts
import re
import rotomask
import tlineaction
import tlinewidgets
import trimmodes
import updater
import projectaction
import workflow
# ------------------------------------- keyboard events
def key_down(widget, event):
"""
Global key press listener.
"""
# Handle ESCAPE
if event.keyval == Gdk.KEY_Escape:
if audiowaveform.waveform_thread != None:
audiowaveform.waveform_thread.abort_rendering()
return True
elif editorstate.current_is_move_mode() == False:
modesetting.set_default_edit_mode()
return True
elif gui.big_tc.get_visible_child_name() == "BigTCEntry":
gui.big_tc.set_visible_child_name("BigTCDisplay")
return True
# Compositor editors keyevents
was_handled = _handle_geometry_editor_keys(event)
if was_handled:
# Stop widget focus from travelling if arrow key pressed
gui.editor_window.window.emit_stop_by_name("key_press_event")
return True
was_handled = _handle_effects_editor_keys(event)
if was_handled:
# Stop widget focus from travelling if arrow key pressed
gui.editor_window.window.emit_stop_by_name("key_press_event")
return True
# If timeline widgets are in focus timeline keyevents are available
if _timeline_has_focus():
was_handled = _handle_tline_key_event(event)
if was_handled:
# Stop widget focus from travelling if arrow key pressed for next frame
# by stopping signal
gui.editor_window.window.emit_stop_by_name("key_press_event")
return was_handled
# Insert shortcut keys need more focus then timeline shortcuts.
# these may already have been handled in timeline focus events
was_handled = _handle_extended_monitor_focus_events(event)
if was_handled:
# Stop event handling here
return True
was_handled = _handle_configurable_global_events(event)
if was_handled:
return True
# Pressing timeline button obivously leaves user expecting
# to have focus in timeline
if gui.monitor_switch.widget.has_focus() and timeline_visible():
_handle_tline_key_event(event)
return True
if gui.monitor_switch.widget.has_focus() and (not timeline_visible()):
_handle_clip_key_event(event)
return True
if gui.pos_bar.widget.is_focus() and (not timeline_visible()):
_handle_clip_key_event(event)
return True
# Handle non-timeline delete
if event.keyval == Gdk.KEY_Delete:
return _handle_delete()
# Home
if event.keyval == Gdk.KEY_Home:
if PLAYER().is_playing():
monitorevent.stop_pressed()
PLAYER().seek_frame(0)
_move_to_beginning()
return True
# End
if event.keyval == Gdk.KEY_End:
if PLAYER().is_playing():
monitorevent.stop_pressed()
PLAYER().seek_end()
_move_to_end()
return True
# Select all with CTRL + A in media panel
if event.keyval == Gdk.KEY_a:
if (event.get_state() & Gdk.ModifierType.CONTROL_MASK):
if gui.media_list_view.widget.has_focus() or gui.media_list_view.widget.get_focus_child() != None:
gui.media_list_view.select_all()
return True
if event.keyval == Gdk.KEY_F11:
menuactions.toggle_fullscreen()
return True
#debug.test help
if event.keyval == Gdk.KEY_F12:
if (event.get_state() & Gdk.ModifierType.CONTROL_MASK):
rotomask.show_rotomask()
return True
# Key event was not handled here.
return False
def _timeline_has_focus():
if(gui.tline_canvas.widget.has_focus()
or gui.tline_column.widget.has_focus()
or gui.editor_window.tool_selector.widget.has_focus()
or (gui.pos_bar.widget.has_focus() and timeline_visible())
or gui.tline_scale.widget.has_focus()
or glassbuttons.focus_group_has_focus(glassbuttons.DEFAULT_FOCUS_GROUP)):
return True
return False
def _handle_tline_key_event(event):
"""
This is called when timeline widgets have focus and key is pressed.
Returns True for handled key presses to stop those
keyevents from going forward.
"""
tool_was_selected = workflow.tline_tool_keyboard_selected(event)
if tool_was_selected == True:
return True
action = _get_shortcut_action(event)
prefs = editorpersistance.prefs
if action == 'mark_in':
monitorevent.mark_in_pressed()
return True
if action == 'to_mark_in':
monitorevent.to_mark_in_pressed()
return True
if action == 'zoom_out':
updater.zoom_out()
if action == 'zoom_in':
updater.zoom_in()
if action == 'mark_out':
monitorevent.mark_out_pressed()
return True
if action == 'to_mark_out':
monitorevent.to_mark_out_pressed()
return True
if action == 'play_pause':
if PLAYER().is_playing():
monitorevent.stop_pressed()
else:
monitorevent.play_pressed()
return True
if action == 'switch_monitor':
updater.switch_monitor_display()
return True
if action == 'add_marker':
tlineaction.add_marker()
return True
if action == 'cut':
tlineaction.cut_pressed()
return True
if action == 'cut_all':
tlineaction.cut_all_pressed()
return True
if action == 'sequence_split':
tlineaction.sequence_split_pressed()
return True
if action == 'log_range':
medialog.log_range_clicked()
return True
if action == 'toggle_ripple':
gui.editor_window.toggle_trim_ripple_mode()
return True
# Key bindings for keyboard trimming
if editorstate.current_is_active_trim_mode() == True:
if action == 'prev_frame':
trimmodes.left_arrow_pressed((event.get_state() & Gdk.ModifierType.CONTROL_MASK))
return True
elif action == 'next_frame':
trimmodes.right_arrow_pressed((event.get_state() & Gdk.ModifierType.CONTROL_MASK))
return True
elif action == 'enter_edit':
trimmodes.enter_pressed()
return True
if editorstate.EDIT_MODE() == editorstate.OVERWRITE_MOVE:
if action == 'nudge_back':
movemodes.nudge_selection(-1)
return True
elif action == 'nudge_forward':
movemodes.nudge_selection(1)
return True
elif action == 'nudge_back_10':
movemodes.nudge_selection(-10)
return True
elif action == 'nudge_forward_10':
movemodes.nudge_selection(10)
return True
if editorstate.EDIT_MODE() == editorstate.MULTI_TRIM:
multitrimmode.enter_pressed()
# Key bindings for MOVE MODES and _NO_EDIT modes
if editorstate.current_is_move_mode() or editorstate.current_is_active_trim_mode() == False:
if action == 'next_cut':
if editorstate.timeline_visible():
tline_frame = PLAYER().tracktor_producer.frame()
frame = current_sequence().find_next_cut_frame(tline_frame)
if frame != -1:
PLAYER().seek_frame(frame)
if editorpersistance.prefs.center_on_arrow_move == True:
updater.center_tline_to_current_frame()
return True
else:
monitorevent.up_arrow_seek_on_monitor_clip()
if action == 'prev_cut':
if editorstate.timeline_visible():
tline_frame = PLAYER().tracktor_producer.frame()
frame = current_sequence().find_prev_cut_frame(tline_frame)
if frame != -1:
PLAYER().seek_frame(frame)
if editorpersistance.prefs.center_on_arrow_move == True:
updater.center_tline_to_current_frame()
return True
else:
monitorevent.down_arrow_seek_on_monitor_clip()
return True
# Apr-2017 - SvdB - Add different speeds for different modifiers
# Allow user to select what speed belongs to what modifier, knowing that a combo of mods
# will MULTIPLY all speeds
# Available: SHIFT_MASK LOCK_MASK CONTROL_MASK
if action == 'prev_frame' or action == 'next_frame':
if action == 'prev_frame':
seek_amount = -1
else:
seek_amount = 1
if (event.get_state() & Gdk.ModifierType.SHIFT_MASK):
seek_amount = seek_amount * prefs.ffwd_rev_shift
if (event.get_state() & Gdk.ModifierType.CONTROL_MASK):
seek_amount = seek_amount * prefs.ffwd_rev_ctrl
if (event.get_state() & Gdk.ModifierType.LOCK_MASK):
seek_amount = seek_amount * prefs.ffwd_rev_caps
PLAYER().seek_delta(seek_amount)
return True
if action == '3_point_overwrite':
tlineaction.three_point_overwrite_pressed()
return True
if action == 'overwrite_range':
tlineaction.range_overwrite_pressed()
return True
if action == 'insert':
if not (event.get_state() & Gdk.ModifierType.CONTROL_MASK):
tlineaction.insert_button_pressed()
return True
if action == 'append':
tlineaction.append_button_pressed()
return True
if action == 'append_from_bin':
projectaction.append_selected_media_clips_into_timeline()
return True
if action == 'slower':
monitorevent.j_pressed()
return True
if action == 'stop':
monitorevent.k_pressed()
return True
if action == 'faster':
monitorevent.l_pressed()
return True
if action == 'log_range':
medialog.log_range_clicked()
return True
if action == 'resync':
tlineaction.resync_button_pressed()
return True
if action == 'delete':
if editorstate.EDIT_MODE() == editorstate.KF_TOOL:
kftoolmode.delete_active_keyframe()
else:
# Clip selection and compositor selection are mutually exclusive,
# so max one one these will actually delete something
tlineaction.splice_out_button_pressed()
compositormodes.delete_current_selection()
if action == 'lift':
tlineaction.lift_button_pressed()
return True
if action == 'to_start':
if PLAYER().is_playing():
monitorevent.stop_pressed()
PLAYER().seek_frame(0)
_move_to_beginning()
return True
if action == 'to_end':
if PLAYER().is_playing():
monitorevent.stop_pressed()
PLAYER().seek_end()
_move_to_end()
return True
else:
if action == 'to_start':
if PLAYER().is_playing():
monitorevent.stop_pressed()
gui.editor_window.set_default_edit_tool()
PLAYER().seek_frame(0)
_move_to_beginning()
return True
if action == 'to_end':
if PLAYER().is_playing():
monitorevent.stop_pressed()
gui.editor_window.set_default_edit_tool()
PLAYER().seek_end()
_move_to_end()
return True
return False
def _handle_extended_monitor_focus_events(event):
# This function was added to get a subset of events only to work when monitor has focus
# Apr-2017 - SvdB - For keyboard shortcuts
action = _get_shortcut_action(event)
# We're dropping monitor window in 2 window mode as part of timeline focus
# TODO: gui.sequence_editor_b.has_focus() or
# gui.clip_editor_b.has_focus()):
if not(gui.monitor_switch.widget.has_focus() or gui.pos_bar.widget.has_focus()):
return False
if action == '3_point_overwrite':
tlineaction.three_point_overwrite_pressed()
return True
if action == 'overwrite_range':
tlineaction.range_overwrite_pressed()
if action == 'insert':
tlineaction.insert_button_pressed()
return True
if action == 'append':
tlineaction.append_button_pressed()
return True
if action == 'slower':
monitorevent.j_pressed()
return True
if action == 'stop':
monitorevent.k_pressed()
return True
if action == 'faster':
monitorevent.l_pressed()
return True
if action == 'log_range':
medialog.log_range_clicked()
return True
if action == 'switch_monitor':
updater.switch_monitor_display()
return True
if action == 'append_from_bin':
projectaction.append_selected_media_clips_into_timeline()
return True
tool_was_selected = workflow.tline_tool_keyboard_selected(event)
if tool_was_selected == True:
return True
return False
# Apr-2017 - SvdB
def _get_shortcut_action(event):
# Get the name of the key pressed
key_name = Gdk.keyval_name(event.keyval).lower()
# Check if this key is in the dictionary.
state = event.get_state()
# Now we have a key and a key state we need to check if it is a shortcut.
# If it IS a shortcut we need to determine what action to take
if key_name in shortcuts._keyboard_actions:
# Now get the associated dictionary
_secondary_dict = shortcuts._keyboard_actions[key_name]
# In order to check for all available combinations of Ctrl+Alt etc (CTRL+ALT should be the same as ALT_CTRL)
# we do a SORT on the string. So both CTRL+ALT and ALT+CTRL will become +ACLLRTT and can be easily compared
modifier = ""
if state & Gdk.ModifierType.CONTROL_MASK:
modifier = "CTRL"
if state & Gdk.ModifierType.MOD1_MASK:
if modifier != "":
modifier = modifier + "+"
modifier = modifier + "ALT"
if state & Gdk.ModifierType.SHIFT_MASK:
if modifier != "":
modifier = modifier + "+"
modifier = modifier + "SHIFT"
# CapsLock is used as an equivalent to SHIFT, here
if state & Gdk.ModifierType.LOCK_MASK:
if modifier != "":
modifier = modifier + "+"
modifier = modifier + "SHIFT"
# Set to None if no modifier found
if modifier == "":
modifier = 'None'
try:
action = _secondary_dict[''.join(sorted(re.sub('[\s]','',modifier.lower())))]
except:
try:
action = _secondary_dict[''.join(sorted(re.sub('[\s]','','Any'.lower())))]
except:
action = 'None'
return action
# We didn't find an action, so return nothing
return 'None'
def _handle_configurable_global_events(event):
action = _get_shortcut_action(event)
if action == 'open_next':
projectaction.open_next_media_item_in_monitor()
return True
return False
def _handle_clip_key_event(event):
# Key bindings for MOVE MODES
if editorstate.current_is_move_mode():
action = _get_shortcut_action(event)
# Apr-2017 - SvdB - Add different speeds for different modifiers
# Allow user to select what speed belongs to what modifier, knowing that a combo of mods
# will MULTIPLY all speeds
# Available: SHIFT_MASK LOCK_MASK CONTROL_MASK
prefs = editorpersistance.prefs
if action == 'prev_frame' or action == 'next_frame':
if action == 'prev_frame':
seek_amount = -1
else:
seek_amount = 1
if (event.get_state() & Gdk.ModifierType.SHIFT_MASK):
seek_amount = seek_amount * prefs.ffwd_rev_shift
if (event.get_state() & Gdk.ModifierType.CONTROL_MASK):
seek_amount = seek_amount * prefs.ffwd_rev_ctrl
if (event.get_state() & Gdk.ModifierType.LOCK_MASK):
seek_amount = seek_amount * prefs.ffwd_rev_caps
PLAYER().seek_delta(seek_amount)
return True
if action == 'next_cut':
if editorstate.timeline_visible():
tline_frame = PLAYER().tracktor_producer.frame()
frame = current_sequence().find_next_cut_frame(tline_frame)
if frame != -1:
PLAYER().seek_frame(frame)
if editorpersistance.prefs.center_on_arrow_move == True:
updater.center_tline_to_current_frame()
return True
else:
monitorevent.up_arrow_seek_on_monitor_clip()
return True
if action == 'prev_cut':
if editorstate.timeline_visible():
tline_frame = PLAYER().tracktor_producer.frame()
frame = current_sequence().find_prev_cut_frame(tline_frame)
if frame != -1:
PLAYER().seek_frame(frame)
if editorpersistance.prefs.center_on_arrow_move == True:
updater.center_tline_to_current_frame()
return True
else:
monitorevent.down_arrow_seek_on_monitor_clip()
return True
if action == 'play_pause':
if PLAYER().is_playing():
monitorevent.stop_pressed()
else:
monitorevent.play_pressed()
if action == 'mark_in':
monitorevent.mark_in_pressed()
return True
if action == 'to_mark_in':
monitorevent.to_mark_in_pressed()
return True
if action == 'mark_out':
monitorevent.mark_out_pressed()
return True
if action == 'to_mark_out':
monitorevent.to_mark_out_pressed()
return True
def _handle_delete():
# Delete media file
if gui.media_list_view.widget.get_focus_child() != None:
projectaction.delete_media_files()
return True
# Delete bin
if gui.bin_list_view.get_focus_child() != None:
if gui.bin_list_view.text_rend_1.get_property("editing") == True:
return False
projectaction.delete_selected_bin()
return True
# Delete sequence
if gui.sequence_list_view.get_focus_child() != None:
if gui.sequence_list_view.text_rend_1.get_property("editing") == True:
return False
projectaction.delete_selected_sequence()
return True
# Delete effect
if gui.effect_stack_list_view.get_focus_child() != None:
clipeffectseditor.delete_effect_pressed()
return True
# Delete media log event
if gui.editor_window.media_log_events_list_view.get_focus_child() != None:
medialog.delete_selected()
return True
focus_editor = _get_focus_keyframe_editor(compositeeditor.keyframe_editor_widgets)
if focus_editor != None:
focus_editor.delete_pressed()
return True
focus_editor = _get_focus_keyframe_editor(clipeffectseditor.keyframe_editor_widgets)
if focus_editor != None:
focus_editor.delete_pressed()
return True
return False
def _handle_geometry_editor_keys(event):
if compositeeditor.keyframe_editor_widgets != None:
for kfeditor in compositeeditor.keyframe_editor_widgets:
if kfeditor.get_focus_child() != None:
if kfeditor.__class__ == keyframeeditor.GeometryEditor or \
kfeditor.__class__ == keyframeeditor.RotatingGeometryEditor:
# Apr-2017 - SvdB - For keyboard shortcuts. I have NOT changed the arrow keys for
# the kfeditor action. That didn't seem appropriate
action = _get_shortcut_action(event)
if ((event.keyval == Gdk.KEY_Left)
or (event.keyval == Gdk.KEY_Right)
or (event.keyval == Gdk.KEY_Up)
or (event.keyval == Gdk.KEY_Down)):
kfeditor.arrow_edit(event.keyval, (event.get_state() & Gdk.ModifierType.CONTROL_MASK), (event.get_state() & Gdk.ModifierType.SHIFT_MASK))
return True
if event.keyval == Gdk.KEY_plus:
pass # not impl
if action == 'play_pause':
if PLAYER().is_playing():
monitorevent.stop_pressed()
else:
monitorevent.play_pressed()
return True
return False
def _handle_effects_editor_keys(event):
action = _get_shortcut_action(event)
focus_editor = _get_focus_keyframe_editor(clipeffectseditor.keyframe_editor_widgets)
if focus_editor != None:
if action == 'play_pause':
if PLAYER().is_playing():
monitorevent.stop_pressed()
else:
monitorevent.play_pressed()
return True
if action == 'prev_frame' or action == 'next_frame':
prefs = editorpersistance.prefs
if action == 'prev_frame':
seek_amount = -1
else:
seek_amount = 1
if (event.get_state() & Gdk.ModifierType.SHIFT_MASK):
seek_amount = seek_amount * prefs.ffwd_rev_shift
if (event.get_state() & Gdk.ModifierType.CONTROL_MASK):
seek_amount = seek_amount * prefs.ffwd_rev_ctrl
if (event.get_state() & Gdk.ModifierType.LOCK_MASK):
seek_amount = seek_amount * prefs.ffwd_rev_caps
PLAYER().seek_delta(seek_amount)
return True
return False
def _get_focus_keyframe_editor(keyframe_editor_widgets):
if keyframe_editor_widgets == None:
return None
for kfeditor in keyframe_editor_widgets:
if kfeditor.get_focus_child() != None:
return kfeditor
return None
def _move_to_beginning():
tlinewidgets.pos = 0
updater.repaint_tline()
updater.update_tline_scrollbar()
def _move_to_end():
updater.repaint_tline()
updater.update_tline_scrollbar()
# ----------------------------------------------------------------------- COPY PASTE ACTION FORWARDING
def copy_action():
if _timeline_has_focus() == False:
filter_kf_editor = _get_focus_keyframe_editor(clipeffectseditor.keyframe_editor_widgets)
geom_kf_editor = _get_focus_keyframe_editor(compositeeditor.keyframe_editor_widgets)
if filter_kf_editor != None:
value = filter_kf_editor.get_copy_kf_value()
save_data = (appconsts.COPY_PASTE_KEYFRAME_EDITOR_KF_DATA, (value, filter_kf_editor))
editorstate.set_copy_paste_objects(save_data)
elif geom_kf_editor != None:
value = geom_kf_editor.get_copy_kf_value()
save_data = (appconsts.COPY_PASTE_GEOMETRY_EDITOR_KF_DATA, (value, geom_kf_editor))
editorstate.set_copy_paste_objects(save_data)
else:
# Try to extract text to clipboard because user pressed CTRL + C
copy_source = gui.editor_window.window.get_focus()
try:
copy_source.copy_clipboard()
except:# selected widget was not a Gtk.Editable that can provide text to clipboard
pass
else:
tlineaction.do_timeline_objects_copy()
def paste_action():
if _timeline_has_focus() == False:
copy_paste_object = editorstate.get_copy_paste_objects()
if copy_paste_object == None:
return
data_type, paste_data = editorstate.get_copy_paste_objects()
if data_type == appconsts.COPY_PASTE_KEYFRAME_EDITOR_KF_DATA:
value, kf_editor = paste_data
kf_editor.paste_kf_value(value)
elif data_type == appconsts.COPY_PASTE_GEOMETRY_EDITOR_KF_DATA:
value, geom_editor = paste_data
geom_editor.paste_kf_value(value)
else:
tlineaction.do_timeline_objects_paste()
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/keyframeeditcanvas.py 0000664 0000000 0000000 00000121627 13610327166 0027333 0 ustar 00root root 0000000 0000000 """
Flowblade Movie Editor is a nonlinear video editor.
Copyright 2012 Janne Liljeblad.
This file is part of Flowblade Movie Editor .
Flowblade Movie Editor 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.
Flowblade Movie Editor is distributed in the hope that 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 Flowblade Movie Editor. If not, see .
"""
"""
Module contains GUI widgets used edit geometries on canvas with a mouse.
"""
import copy
import math
from gi.repository import Gdk
import cairoarea
import utils
import viewgeom
EP_HALF = 4
GEOMETRY_EDITOR_WIDTH = 250
GEOMETRY_EDITOR_HEIGHT = 200
# Rectangle edit handles ids. Points numbered in clockwise direction
# to get opposite points easily.
TOP_LEFT = 0
TOP_MIDDLE = 1
TOP_RIGHT = 2
MIDDLE_RIGHT = 3
BOTTOM_RIGHT = 4
BOTTOM_MIDDLE = 5
BOTTOM_LEFT = 6
MIDDLE_LEFT = 7
# Rotating rectangle handle ids
POS_HANDLE = 0
X_SCALE_HANDLE = 1
Y_SCALE_HANDLE = 2
ROTATION_HANDLE = 3
# Hit values for rect, edit point hits return edit point id
AREA_HIT = 9
NO_HIT = 10
EDITABLE_RECT_COLOR = (0,0,0)
_shift_down = None
# -------------------------------------------------------------- shape objects
class EditRect:
"""
Line box with corner and middle handles that user can use to set
position, width and height of rectangle geometry.
"""
def __init__(self, x, y, w, h):
self.edit_points = {}
self.x = x
self.y = y
self.w = w
self.h = h
self.start_x = None
self.start_y = None
self.start_w = None
self.start_h = None
self.start_op_x = None
self.start_op_y = None
self.projection_point = None
self.set_edit_points()
def set_geom(self, x, y, w, h):
self.x = x
self.y = y
self.w = w
self.h = h
self.set_edit_points()
def set_edit_points(self):
self.edit_points[TOP_LEFT] = (self.x, self.y)
self.edit_points[TOP_MIDDLE] = (self.x + self.w/2, self.y)
self.edit_points[TOP_RIGHT] = (self.x + self.w, self.y)
self.edit_points[MIDDLE_LEFT] = (self.x, self.y + self.h/2)
self.edit_points[MIDDLE_RIGHT] = (self.x + self.w, self.y + self.h/2)
self.edit_points[BOTTOM_LEFT] = (self.x, self.y + self.h)
self.edit_points[BOTTOM_MIDDLE] = (self.x + self.w/2, self.y + self.h)
self.edit_points[BOTTOM_RIGHT] = (self.x + self.w, self.y + self.h)
def check_hit(self, x, y):
for id_int, value in self.edit_points.items():
x1, y1 = value
if (x >= x1 - EP_HALF and x <= x1 + EP_HALF and y >= y1 - EP_HALF and y <= y1 + EP_HALF):
return id_int
x1, y1 = self.edit_points[TOP_LEFT]
x2, y2 = self.edit_points[BOTTOM_RIGHT]
if (x >= x1 and x <= x2 and y >= y1 and y <= y2):
return AREA_HIT
return NO_HIT
def edit_point_drag_started(self, ep_id):
opposite_id = (ep_id + 4) % 8
self.drag_ep = ep_id
self.guide_line = viewgeom.get_line_for_points( self.edit_points[ep_id],
self.edit_points[opposite_id])
x, y = self.edit_points[ep_id]
self.start_x = x
self.start_y = y
opx, opy = self.edit_points[opposite_id]
self.start_op_x = opx
self.start_op_y = opy
self.start_w = self.w
self.start_h = self.h
self.projection_point = (x, y)
def edit_point_drag(self, delta_x, delta_y):
x = self.start_x + delta_x
y = self.start_y + delta_y
p = (x, y)
lx, ly = self.guide_line.get_normal_projection_point(p)
self.projection_point = (lx, ly)
# Set new rect
if self.drag_ep == TOP_LEFT:
self.x = lx
self.y = ly
self.w = self.start_op_x - lx
self.h = self.start_op_y - ly
elif self.drag_ep == BOTTOM_RIGHT:
self.x = self.start_op_x
self.y = self.start_op_y
self.w = lx - self.start_op_x
self.h = ly - self.start_op_y
elif self.drag_ep == BOTTOM_LEFT:
self.x = lx
self.y = self.start_op_y
self.w = self.start_op_x - lx
self.h = ly - self.start_op_y
elif self.drag_ep == TOP_RIGHT:
self.x = self.start_op_x
self.y = ly
self.w = lx - self.start_op_x
self.h = self.start_op_y - ly
elif self.drag_ep == MIDDLE_RIGHT:
self.x = self.start_op_x
self.y = self.start_op_y - (self.start_h / 2.0)
self.w = lx - self.start_op_x
self.h = self.start_h
elif self.drag_ep == MIDDLE_LEFT:
self.x = lx
self.y = self.start_y - (self.start_h / 2.0)
self.w = self.start_op_x - lx
self.h = self.start_h
elif self.drag_ep == TOP_MIDDLE:
self.x = self.start_x - (self.start_w / 2.0)
self.y = ly
self.w = self.start_w
self.h = self.start_op_y - ly
elif self.drag_ep == BOTTOM_MIDDLE:
self.x = self.start_op_x - (self.start_w / 2.0)
self.y = self.start_op_y
self.w = self.start_w
self.h = ly - self.start_op_y
# No negative size
if self.w < 1.0:
self.w = 1.0
if self.h < 1.0:
self.h = 1.0
self.set_edit_points()
def clear_projection_point(self):
self.projection_point = None
def move_started(self):
self.start_x = self.x
self.start_y = self.y
def move_drag(self, delta_x, delta_y):
self.x = self.start_x + delta_x
self.y = self.start_y + delta_y
self.set_edit_points()
def draw(self, cr):
# Box
cr.set_line_width(1.0)
color = EDITABLE_RECT_COLOR
cr.set_source_rgb(*color)
cr.rectangle(self.x + 0.5, self.y + 0.5, self.w, self.h)
cr.stroke()
# handles
for id_int, pos in self.edit_points.items():
x, y = pos
cr.rectangle(x - 2, y - 2, 4, 4)
cr.fill()
if self.projection_point != None:
x, y = self.projection_point
cr.set_source_rgb(0,1,0)
cr.rectangle(x - 2, y - 2, 4, 4)
cr.fill()
# ---------------------------------------------------- screen editors
def _geom_kf_sort(kf):
"""
Function is used to sort keyframes by frame number.
"""
frame, shape, opacity = kf
return frame
class AbstractEditCanvas:
"""
Base class for editors used to edit something on top of rectangle representing
screen.
parent_editor needs to implement interface
mouse_scroll_up()
mouse_scroll_down()
geometry_edit_started()
update_request_from_geom_editor()
queue_draw()
geometry_edit_finished()
"""
def __init__(self, editable_property, parent_editor):
self.widget = cairoarea.CairoDrawableArea2( GEOMETRY_EDITOR_WIDTH,
GEOMETRY_EDITOR_HEIGHT,
self._draw)
self.widget.press_func = self._press_event
self.widget.motion_notify_func = self._motion_notify_event
self.widget.release_func = self._release_event
self.widget.mouse_scroll_func = self._mouse_scroll_listener
self.clip_length = editable_property.get_clip_length()
self.pixel_aspect_ratio = editable_property.get_pixel_aspect_ratio()
self.current_clip_frame = 0
# Keyframe tuples are of type (frame, rect, opacity)
self.keyframes = None # Set using function AbstractScreenEditor.set_keyframes(). Keyframes are in form [frame, shape, opacity]
self.keyframe_parser = None # Function used to parse keyframes to tuples is different for different expressions
# Parent editor sets this.
self.current_mouse_hit = None
self.start_x = None
self.start_Y = None
self.parent_editor = parent_editor
self.source_width = -1 # unscaled source image width, set later
self.source_height = -1 # unscaled source image height, set later
self.coords = None # Calculated later when we have allocation available
def init_editor(self, source_width, source_height, y_fract):
self.source_width = source_width
self.source_height = source_height
self.y_fract = y_fract
self.screen_ratio = float(source_width) / float(source_height)
# ---------------------------------------------------- draw params
def _create_coords(self):
self.coords = utils.EmptyClass()
panel_w = self.widget.get_allocation().width
panel_h = self.widget.get_allocation().height
self.coords.screen_h = panel_h * self.y_fract
self.coords.screen_w = self.coords.screen_h * self.screen_ratio * self.pixel_aspect_ratio
self.coords.orig_x = (panel_w - self.coords.screen_w) / 2.0
self.coords.orig_y = (panel_h - self.coords.screen_h) / 2.0
self.coords.x_scale = self.source_width / self.coords.screen_w
self.coords.y_scale = self.source_height / self.coords.screen_h
def set_view_size(self, y_fract):
self.y_fract = y_fract
self._create_coords()
def get_screen_x(self, x):
p_x_from_origo = x - self.coords.orig_x
return p_x_from_origo * self.coords.x_scale
def get_screen_y(self, y):
p_y_from_origo = y - self.coords.orig_y
return p_y_from_origo * self.coords.y_scale
def get_panel_point(self, x, y):
px = self.coords.orig_x + x / self.coords.x_scale
py = self.coords.orig_y + y / self.coords.y_scale
return (px, py)
# --------------------------------------------------------- updates
def set_clip_frame(self, frame):
self.current_clip_frame = frame
self._clip_frame_changed()
def _clip_frame_changed(self):
print("_clip_frame_changed not impl")
def set_keyframe_to_edit_shape(self, kf_index, value_shape=None):
if value_shape == None:
value_shape = self._get_current_screen_shape()
frame, shape, opacity = self.keyframes[kf_index]
self.keyframes.pop(kf_index)
new_kf = (frame, value_shape, opacity)
self.keyframes.append(new_kf)
self.keyframes.sort(key=_geom_kf_sort)
self._update_shape()
def _get_current_screen_shape(self):
print("_get_current_screen_shape not impl")
def _update_shape(self):
print("_update_shape not impl")
# ------------------------------------------------- keyframes
def add_keyframe(self, frame):
if self._frame_has_keyframe(frame) == True:
return
# Get previous keyframe
prev_kf = None
for i in range(0, len(self.keyframes)):
p_frame, p_shape, p_opacity = self.keyframes[i]
if p_frame < frame:
prev_kf = self.keyframes[i]
if prev_kf == None:
prev_kf = self.keyframes[len(self.keyframes) - 1]
# Add with values of previous
p_frame, p_shape, p_opacity = prev_kf
self.keyframes.append((frame, copy.deepcopy(p_shape), copy.deepcopy(p_opacity)))
self.keyframes.sort(key=_geom_kf_sort)
def delete_active_keyframe(self, keyframe_index):
if keyframe_index == 0:
# keyframe frame 0 cannot be removed
return
self.keyframes.pop(keyframe_index)
def _frame_has_keyframe(self, frame):
for i in range(0, len(self.keyframes)):
kf = self.keyframes[i]
kf_frame, rect, opacity = kf
if frame == kf_frame:
return True
return False
def set_keyframes(self, keyframes_str, out_to_in_func):
self.keyframes = self.keyframe_parser(keyframes_str, out_to_in_func)
def set_keyframe_frame(self, active_kf_index, frame):
old_frame, shape, opacity = self.keyframes[active_kf_index]
self.keyframes.pop(active_kf_index)
self.keyframes.insert(active_kf_index, (frame, shape, opacity))
def get_keyframe(self, kf_index):
return self.keyframes[kf_index]
# ---------------------------------------------------- editor menu actions
def reset_active_keyframe_shape(self, active_kf_index):
print("reset_active_keyframe_shape not impl")
def reset_active_keyframe_rect_shape(self, active_kf_index):
print("reset_active_keyframe_rect_shape not impl")
def center_h_active_keyframe_shape(self, active_kf_index):
print("center_h_active_keyframe_shape not impl")
def center_v_active_keyframe_shape(self, active_kf_index):
print("center_v_active_keyframe_shape not impl")
# ------------------------------------------------------ arrow edit
def handle_arrow_edit(self, keyval):
print("handle_arrow_edit not impl")
# -------------------------------------------------------- mouse events
def _press_event(self, event):
"""
Mouse button callback
"""
self.current_mouse_hit = self._check_shape_hit(event.x, event.y)
if self.current_mouse_hit == NO_HIT:
return
self.mouse_start_x = event.x
self.mouse_start_y = event.y
self._shape_press_event()
self.parent_editor.geometry_edit_started()
self.parent_editor.update_request_from_geom_editor()
def _check_shape_hit(self, x, y):
print("_check_shape_hit not impl")
def _shape_press_event(self):
print("_shape_press_event not impl")
def _motion_notify_event(self, x, y, state):
"""
Mouse move callback
"""
if self.current_mouse_hit == NO_HIT:
return
delta_x = x - self.mouse_start_x
delta_y = y - self.mouse_start_y
global _shift_down
if state & Gdk.ModifierType.SHIFT_MASK:
if abs(x - self.mouse_start_x) < abs(y - self.mouse_start_y):
delta_x = 0
else:
delta_y = 0
_shift_down = (self.mouse_start_x, self.mouse_start_y)
else:
_shift_down = None
self._shape__motion_notify_event(delta_x, delta_y, (state & Gdk.ModifierType.CONTROL_MASK))
self.parent_editor.queue_draw()
def _shape__motion_notify_event(self, delta_x, delta_y, CTRL_DOWN):
print("_shape__motion_notify_event not impl")
def _release_event(self, event):
global _shift_down
_shift_down = None
if self.current_mouse_hit == NO_HIT:
return
delta_x = event.x - self.mouse_start_x
delta_y = event.y - self.mouse_start_y
if event.get_state() & Gdk.ModifierType.SHIFT_MASK:
if abs(event.x - self.mouse_start_x) < abs(event.y - self.mouse_start_y):
delta_x = 0
else:
delta_y = 0
self._shape_release_event(delta_x, delta_y, (event.get_state() & Gdk.ModifierType.CONTROL_MASK))
self.parent_editor.geometry_edit_finished()
def _shape_release_event(self, delta_x, delta_y, CTRL_DOWN):
print("_shape_release_event not impl")
def _mouse_scroll_listener(self, event):
if event.direction == Gdk.ScrollDirection.UP:
self.parent_editor.mouse_scroll_up()
else:
self.parent_editor.mouse_scroll_down()
return True
# ----------------------------------------------- drawing
def _draw(self, event, cr, allocation):
"""
Callback for repaint from CairoDrawableArea.
We get cairo contect and allocation.
"""
if self.coords == None:
self._create_coords()
x, y, w, h = allocation
# Draw bg
cr.set_source_rgb(0.75, 0.75, 0.77)
cr.rectangle(0, 0, w, h)
cr.fill()
# Draw screen
cr.set_source_rgb(0.6, 0.6, 0.6)
cr.rectangle(self.coords.orig_x, self.coords.orig_y,
self.coords.screen_w, self.coords.screen_h)
cr.fill()
if _shift_down != None:
cr.set_source_rgb(0.0, 0.0, 0.77)
cr.set_line_width(1.0)
mx, my = _shift_down
cr.move_to(mx, 0)
cr.line_to(mx, h)
cr.stroke()
cr.move_to(0, my)
cr.line_to(w, my)
cr.stroke()
screen_rect = [self.coords.orig_x, self.coords.orig_y,
self.coords.screen_w, self.coords.screen_h]
self._draw_edge(cr, screen_rect)
self._draw_edit_shape(cr, allocation)
def _draw_edge(self, cr, rect):
cr.set_line_width(1.0)
cr.set_source_rgb(0, 0, 0)
cr.rectangle(rect[0] + 0.5, rect[1] + 0.5, rect[2], rect[3])
cr.stroke()
def _draw_edit_shape(self, cr, allocation):
print("_draw_edit_shape not impl.")
class BoxEditCanvas(AbstractEditCanvas):
"""
GUI component for editing position and scale values of keyframes
of source image in compositors.
Component is used as a part of e.g GeometryEditor, which handles
also keyframe creation and deletion and opacity, and
writing out the keyframes with combined information.
Required parent_editor callback interface:
mouse_scroll_up()
mouse_scroll_down()
geometry_edit_started()
update_request_from_geom_editor()
queue_draw()
geometry_edit_finished()
"""
def __init__(self, editable_property, parent_editor):
AbstractEditCanvas.__init__(self, editable_property, parent_editor)
self.source_edit_rect = None # Created later when we have allocation available
def reset_active_keyframe_shape(self, active_kf_index):
frame, old_rect, opacity = self.keyframes[active_kf_index]
rect = [0, 0, self.source_width, self.source_height]
self.keyframes.pop(active_kf_index)
self.keyframes.insert(active_kf_index, (frame, rect, opacity))
def reset_active_keyframe_rect_shape(self, active_kf_index):
frame, old_rect, opacity = self.keyframes[active_kf_index]
x, y, w, h = old_rect
new_h = int(float(w) * (float(self.source_height) / float(self.source_width)))
rect = [x, y, w, new_h]
self.keyframes.pop(active_kf_index)
self.keyframes.insert(active_kf_index, (frame, rect, opacity))
def center_h_active_keyframe_shape(self, active_kf_index):
frame, old_rect, opacity = self.keyframes[active_kf_index]
ox, y, w, h = old_rect
x = self.source_width / 2 - w / 2
rect = [x, y, w, h ]
self.keyframes.pop(active_kf_index)
self.keyframes.insert(active_kf_index, (frame, rect, opacity))
def center_v_active_keyframe_shape(self, active_kf_index):
frame, old_rect, opacity = self.keyframes[active_kf_index]
x, oy, w, h = old_rect
y = self.source_height / 2 - h / 2
rect = [x, y, w, h ]
self.keyframes.pop(active_kf_index)
self.keyframes.insert(active_kf_index, (frame, rect, opacity))
def _clip_frame_changed(self):
if self.source_edit_rect != None:
self._update_source_rect()
def _update_shape(self):
self._update_source_rect()
def _update_source_rect(self):
for i in range(0, len(self.keyframes)):
frame, rect, opacity = self.keyframes[i]
if frame == self.current_clip_frame:
self.source_edit_rect.set_geom(*self._get_screen_to_panel_rect(rect))
return
try:
# See if frame between this and next keyframe
frame_n, rect_n, opacity_n = self.keyframes[i + 1]
if ((frame < self.current_clip_frame)
and (self.current_clip_frame < frame_n)):
time_fract = float((self.current_clip_frame - frame)) / \
float((frame_n - frame))
frame_rect = self._get_interpolated_rect(rect, rect_n, time_fract)
self.source_edit_rect.set_geom(*self._get_screen_to_panel_rect(frame_rect))
return
except: # past last frame, use its value
self.source_edit_rect.set_geom(*self._get_screen_to_panel_rect(rect))
return
print("reached end of _update_source_rect, this should be unreachable")
def _get_interpolated_rect(self, rect_1, rect_2, fract):
x1, y1, w1, h1 = rect_1
x2, y2, w2, h2 = rect_2
x = x1 + (x2 - x1) * fract
y = y1 + (y2 - y1) * fract
w = w1 + (w2 - w1) * fract
h = h1 + (h2 - h1) * fract
return (x, y, w, h)
def _get_screen_to_panel_rect(self, rect):
x, y, w, h = rect
px = self.coords.orig_x + x / self.coords.x_scale
py = self.coords.orig_y + y / self.coords.y_scale
pw = w / self.coords.x_scale # scale is panel to screen, this is screen to panel
ph = h / self.coords.y_scale # scale is panel to screen, this is screen to panel
return (px, py, pw, ph)
def _get_current_screen_shape(self):
return self._get_source_edit_rect_to_screen_rect()
def _get_source_edit_rect_to_screen_rect(self):
p_x_from_origo = self.source_edit_rect.x - self.coords.orig_x
p_y_from_origo = self.source_edit_rect.y - self.coords.orig_y
screen_x = p_x_from_origo * self.coords.x_scale
screen_y = p_y_from_origo * self.coords.y_scale
screen_w = self.source_edit_rect.w * self.coords.x_scale
screen_h = self.source_edit_rect.h * self.coords.y_scale
return [screen_x, screen_y, screen_w, screen_h]
def _draw_edit_shape(self, cr, allocation):
# Edit rect is created here only when we're sure to have allocation
if self.source_edit_rect == None:
self.source_edit_rect = EditRect(10, 10, 10, 10) # values are immediately overwritten
self._update_source_rect()
# Draw source
self.source_edit_rect.draw(cr)
# ----------------------------------------- mouse press event
def _check_shape_hit(self, x, y):
return self.source_edit_rect.check_hit(x, y)
def _shape_press_event(self):
if self.current_mouse_hit == AREA_HIT:
self.source_edit_rect.move_started()
else:
self.source_edit_rect.edit_point_drag_started(self.current_mouse_hit)
def _shape__motion_notify_event(self, delta_x, delta_y, CTRL_DOWN):
if self.current_mouse_hit == AREA_HIT:
self.source_edit_rect.move_drag(delta_x, delta_y)
else:
self.source_edit_rect.edit_point_drag(delta_x, delta_y)
def _shape_release_event(self, delta_x, delta_y, CTRL_DOWN):
if self.current_mouse_hit == AREA_HIT:
self.source_edit_rect.move_drag(delta_x, delta_y)
else:
self.source_edit_rect.edit_point_drag(delta_x, delta_y)
self.source_edit_rect.clear_projection_point()
def handle_arrow_edit(self, keyval, delta):
if keyval == Gdk.KEY_Left:
self.source_edit_rect.x -= delta
if keyval == Gdk.KEY_Right:
self.source_edit_rect.x += delta
if keyval == Gdk.KEY_Up:
self.source_edit_rect.y -= delta
if keyval == Gdk.KEY_Down:
self.source_edit_rect.y += delta
def handle_arrow_scale_edit(self, keyval, delta):
old_w = self.source_edit_rect.w
if keyval == Gdk.KEY_Left:
self.source_edit_rect.w -= delta
if keyval == Gdk.KEY_Right:
self.source_edit_rect.w += delta
if keyval == Gdk.KEY_Up:
self.source_edit_rect.w -= delta
if keyval == Gdk.KEY_Down:
self.source_edit_rect.w += delta
self.source_edit_rect.h = self.source_edit_rect.h * (self.source_edit_rect.w / old_w)
def print_keyframes(self):
for i in range(0, len(self.keyframes)):
print(self.keyframes[i])
class RotatingEditCanvas(AbstractEditCanvas):
"""
Needed parent_editor callback interface:
mouse_scroll_up()
mouse_scroll_down()
geometry_edit_started()
update_request_from_geom_editor()
queue_draw()
geometry_edit_finished()
Keyframes in form: [frame, [x, y, x_scale, y_scale, rotation] opacity]
"""
def __init__(self, editable_property, parent_editor):
AbstractEditCanvas.__init__(self, editable_property, parent_editor)
self.edit_points = []
self.shape_x = None
self.shape_y = None
self.rotation = None
self.x_scale = None
self.y_scale = None
self.draw_bounding_box = True # This may be set False at creation site.
def create_edit_points_and_values(self):
# creates untransformed edit shape to init array, values will be overridden shortly
self.edit_points.append((self.source_width / 2, self.source_height / 2)) # center
self.edit_points.append((self.source_width, self.source_height / 2)) # x_Scale
self.edit_points.append((self.source_width / 2, 0)) # y_Scale
self.edit_points.append((0, 0)) # rotation
self.edit_points.append((self.source_width, 0)) # top right
self.edit_points.append((self.source_width, self.source_height)) # bottom right
self.edit_points.append((0, self.source_height)) # bottom left
self.untrans_points = copy.deepcopy(self.edit_points)
self.shape_x = self.source_width / 2 # always == self.edit_points[0] x
self.shape_y = self.source_height / 2 # always == self.edit_points[0] y
self.rotation = 0.0
self.x_scale = 1.0
self.y_scale = 1.0
# ------------------------------------------ hit testing
def _check_shape_hit(self, x, y):
edit_panel_points = []
for ep in self.edit_points:
edit_panel_points.append(self.get_panel_point(*ep))
for i in range(0, 4):
if self._check_point_hit((x, y), edit_panel_points[i], 10):
return i #indexes correspond to edit_point_handle indexes
if viewgeom.point_in_convex_polygon((x, y), edit_panel_points[3:7], 0) == True: # corners are edit points 3, 4, 5, 6
return AREA_HIT
return NO_HIT
def _check_point_hit(self, p, ep, TARGET_HALF):
x, y = p
ex, ey = ep
if (x >= ex - TARGET_HALF and x <= ex + TARGET_HALF and y >= ey - TARGET_HALF and y <= ey + TARGET_HALF):
return True
return False
# ------------------------------------------------------- menu edit events
def reset_active_keyframe_shape(self, active_kf_index):
frame, trans, opacity = self.keyframes[active_kf_index]
new_trans = [self.source_width / 2, self.source_height / 2, 1.0, 1.0, 0]
self.keyframes.pop(active_kf_index)
self.keyframes.insert(active_kf_index, (frame, new_trans, opacity))
self._update_shape()
def reset_active_keyframe_rect_shape(self, active_kf_index):
frame, trans, opacity = self.keyframes[active_kf_index]
x, y, x_scale, y_scale, rotation = trans
new_trans = [x, y, x_scale, x_scale, rotation]
self.keyframes.pop(active_kf_index)
self.keyframes.insert(active_kf_index, (frame, new_trans, opacity))
self._update_shape()
def center_h_active_keyframe_shape(self, active_kf_index):
frame, trans, opacity = self.keyframes[active_kf_index]
x, y, x_scale, y_scale, rotation = trans
new_trans = [self.source_width / 2, y, x_scale, y_scale, rotation]
self.keyframes.pop(active_kf_index)
self.keyframes.insert(active_kf_index, (frame, new_trans, opacity))
self._update_shape()
def center_v_active_keyframe_shape(self, active_kf_index):
frame, trans, opacity = self.keyframes[active_kf_index]
x, y, x_scale, y_scale, rotation = trans
new_trans = [x, self.source_height / 2, x_scale, y_scale, rotation]
self.keyframes.pop(active_kf_index)
self.keyframes.insert(active_kf_index, (frame, new_trans, opacity))
self._update_shape()
# -------------------------------------------------------- updating
def _clip_frame_changed(self):
self._update_shape()
def _get_current_screen_shape(self):
return [self.shape_x, self.shape_y, self.x_scale, self.y_scale, self.rotation]
def _update_shape(self):
for i in range(0, len(self.keyframes)):
frame, rect, opacity = self.keyframes[i]
if frame == self.current_clip_frame:
self.set_geom(*rect)
return
try:
# See if frame between this and next keyframe
frame_n, rect_n, opacity_n = self.keyframes[i + 1]
if ((frame < self.current_clip_frame)
and (self.current_clip_frame < frame_n)):
time_fract = float((self.current_clip_frame - frame)) / \
float((frame_n - frame))
frame_rect = self._get_interpolated_rect(rect, rect_n, time_fract)
self.set_geom(*frame_rect)
return
except: # past last frame, use its value ( line: frame_n, rect_n, opacity_n = self.keyframes[i + 1] failed)
self.set_geom(*rect)
return
def set_geom(self, x, y, x_scale, y_scale, rotation):
self.shape_x = x
self.shape_y = y
self.x_scale = x_scale
self.y_scale = y_scale
self.rotation = rotation
self._update_edit_points()
def _get_interpolated_rect(self, rect_1, rect_2, fract):
x1, y1, xs1, ys1, r1 = rect_1
x2, y2, xs2, ys2, r2 = rect_2
x = x1 + (x2 - x1) * fract
y = y1 + (y2 - y1) * fract
xs = xs1 + (xs2 - xs1) * fract
ys = ys1 + (ys2 - ys1) * fract
r = r1 + (r2 - r1) * fract
return (x, y, xs, ys, r)
def handle_arrow_edit(self, keyval, delta):
if keyval == Gdk.KEY_Left:
self.shape_x -= delta
if keyval == Gdk.KEY_Right:
self.shape_x += delta
if keyval == Gdk.KEY_Up:
self.shape_y -= delta
if keyval == Gdk.KEY_Down:
self.shape_y += delta
def handle_arrow_scale_edit(self, keyval, delta):
old_scale = self.x_scale
delta = delta * 0.01
if keyval == Gdk.KEY_Left:
self.x_scale -= delta
if keyval == Gdk.KEY_Right:
self.x_scale += delta
if keyval == Gdk.KEY_Up:
self.x_scale -= delta
if keyval == Gdk.KEY_Down:
self.x_scale += delta
self.y_scale = self.y_scale * (self.x_scale / old_scale)
# --------------------------------------------------------- mouse events
def _shape_press_event(self):
self.start_edit_points = copy.deepcopy(self.edit_points)
if self.current_mouse_hit == X_SCALE_HANDLE:
self.guide = viewgeom.get_vec_for_points((self.shape_x,self.shape_y), self.edit_points[X_SCALE_HANDLE])
elif self.current_mouse_hit == Y_SCALE_HANDLE:
self.guide = viewgeom.get_vec_for_points((self.shape_x,self.shape_y), self.edit_points[Y_SCALE_HANDLE])
elif self.current_mouse_hit == ROTATION_HANDLE:
ax, ay = self.edit_points[POS_HANDLE]
zero_deg_point = (ax, ay + 10)
m_end_point = (self.get_screen_x(self.mouse_start_x), self.get_screen_y(self.mouse_start_y))
self.mouse_start_rotation = viewgeom.get_angle_in_deg(zero_deg_point, self.edit_points[POS_HANDLE], m_end_point)
self.mouse_rotation_last = 0.0
self.rotation_value_start = self.rotation
elif self.current_mouse_hit == POS_HANDLE or self.current_mouse_hit == AREA_HIT:
self.start_shape_x = self.shape_x
self.start_shape_y = self.shape_y
def _shape__motion_notify_event(self, delta_x, delta_y, CTRL_DOWN):
self._update_values_for_mouse_delta(delta_x, delta_y, CTRL_DOWN)
def _shape_release_event(self, delta_x, delta_y, CTRL_DOWN):
self._update_values_for_mouse_delta(delta_x, delta_y, CTRL_DOWN)
def _update_values_for_mouse_delta(self, delta_x, delta_y, CTRL_DOWN):
if self.current_mouse_hit == POS_HANDLE or self.current_mouse_hit == AREA_HIT:
dx = self.get_screen_x(self.coords.orig_x + delta_x)
dy = self.get_screen_y(self.coords.orig_y + delta_y)
self.shape_x = self.start_shape_x + dx
self.shape_y = self.start_shape_y + dy
self._update_edit_points()
elif self.current_mouse_hit == X_SCALE_HANDLE:
dp = self.get_delta_point(delta_x, delta_y, self.edit_points[X_SCALE_HANDLE])
pp = self.guide.get_normal_projection_point(dp)
dist = viewgeom.distance(self.edit_points[POS_HANDLE], pp)
orig_dist = viewgeom.distance(self.untrans_points[POS_HANDLE], self.untrans_points[X_SCALE_HANDLE])
self.x_scale = dist / orig_dist
if CTRL_DOWN:
self.y_scale = self.x_scale
self._update_edit_points()
elif self.current_mouse_hit == Y_SCALE_HANDLE:
dp = self.get_delta_point(delta_x, delta_y, self.edit_points[Y_SCALE_HANDLE])
pp = self.guide.get_normal_projection_point(dp)
dist = viewgeom.distance(self.edit_points[POS_HANDLE], pp)
orig_dist = viewgeom.distance(self.untrans_points[POS_HANDLE], self.untrans_points[Y_SCALE_HANDLE])
self.y_scale = dist / orig_dist
if CTRL_DOWN:
self.x_scale = self.y_scale
self._update_edit_points()
elif self.current_mouse_hit == ROTATION_HANDLE:
ax, ay = self.edit_points[POS_HANDLE]
m_start_point = (self.get_screen_x(self.mouse_start_x), self.get_screen_y(self.mouse_start_y))
m_end_point = (self.get_screen_x(self.mouse_start_x + delta_x), self.get_screen_y(self.mouse_start_y + delta_y))
current_mouse_rotation = self.get_mouse_rotation_angle(self.edit_points[POS_HANDLE], m_start_point, m_end_point)
self.rotation = self.rotation_value_start + current_mouse_rotation
self._update_edit_points()
def get_mouse_rotation_angle(self, anchor, mr_start, mr_end):
angle = viewgeom.get_angle_in_deg(mr_start, anchor, mr_end)
clockw = viewgeom.points_clockwise(mr_start, anchor, mr_end)
if not clockw:
angle = -angle
# Crossed angle for 180 -> 181... range
crossed_angle = angle + 360.0
# Crossed angle for -180 -> 181 ...range.
if angle > 0:
crossed_angle = -360.0 + angle
# See if crossed angle closer to last angle.
if abs(self.mouse_rotation_last - crossed_angle) < abs(self.mouse_rotation_last - angle):
angle = crossed_angle
# Set last to get good results next time.
self.mouse_rotation_last = angle
return angle
def get_delta_point(self, delta_x, delta_y, ep):
dx = self.get_screen_x(self.coords.orig_x + delta_x)
dy = self.get_screen_y(self.coords.orig_y + delta_y)
sx = self.get_screen_x(self.mouse_start_x)
sy = self.get_screen_y(self.mouse_start_y)
return (sx + dx, sy + dy)
def _update_edit_points(self):
self.edit_points = copy.deepcopy(self.untrans_points) #reset before transform
self._translate_edit_points()
self._scale_edit_points()
self._rotate_edit_points()
def _translate_edit_points(self):
ux, uy = self.untrans_points[0]
dx = self.shape_x - ux
dy = self.shape_y - uy
for i in range(0,len(self.edit_points)):
sx, sy = self.untrans_points[i]
self.edit_points[i] = (sx + dx, sy + dy)
def _scale_edit_points(self):
ax, ay = self.edit_points[0]
sax, say = self.untrans_points[0]
for i in range(1, 7):
sx, sy = self.untrans_points[i]
x = ax + self.x_scale * (sx - sax)
y = ay + self.y_scale * (sy - say)
self.edit_points[i] = (x, y)
def _rotate_edit_points(self):
ax, ay = self.edit_points[0]
for i in range(1, 7):
x, y = viewgeom.rotate_point_around_point(self.rotation, self.edit_points[i], self.edit_points[0])
self.edit_points[i] = (x, y)
def _draw_edit_shape(self, cr, allocation):
if self.draw_bounding_box == True:
x, y = self.get_panel_point(*self.edit_points[3])
cr.move_to(x, y)
for i in range(4,7):
x, y = self.get_panel_point(*self.edit_points[i])
cr.line_to(x, y)
cr.close_path()
cr.stroke()
else:
x, y = self.get_panel_point(*self.edit_points[0])
x2, y2 = self.get_panel_point(*self.edit_points[2])
cr.move_to(x, y)
cr.line_to(x2, y2)
cr.set_line_width(1.0)
cr.stroke()
x2, y2 = self.get_panel_point(*self.edit_points[1])
cr.move_to(x, y)
cr.line_to(x2, y2)
cr.set_line_width(1.0)
cr.stroke()
x2, y2 = self.get_panel_point(*self.edit_points[3])
cr.move_to(x, y)
cr.line_to(x2, y2)
cr.set_line_width(1.0)
cr.stroke()
# center cross
#cr.save()
"""
x, y = self.get_panel_point(*self.edit_points[0])
cr.translate(x,y)
cr.rotate(math.radians(self.rotation))
CENTER_ = 3
cr.move_to(-0.5, -CROSS_LENGTH-0.5)
cr.line_to(-0.5, CROSS_LENGTH-0.5)
cr.set_line_width(1.0)
cr.stroke()
cr.move_to(-CROSS_LENGTH - 0.5, -0.5)
cr.line_to(CROSS_LENGTH - 0.5, -0.5)
cr.stroke()
cr.restore()
"""
self._draw_scale_arrow(cr, self.edit_points[2], 90)
self._draw_scale_arrow(cr, self.edit_points[1], 0)
# center cross
cr.save()
x, y = self.get_panel_point(*self.edit_points[0])
cr.translate(x,y)
cr.rotate(math.radians(self.rotation))
CROSS_LENGTH = 3
cr.move_to(-0.5, -CROSS_LENGTH-0.5)
cr.line_to(-0.5, CROSS_LENGTH-0.5)
cr.set_line_width(1.0)
cr.stroke()
cr.move_to(-CROSS_LENGTH - 0.5, -0.5)
cr.line_to(CROSS_LENGTH - 0.5, -0.5)
cr.stroke()
cr.restore()
# roto handle
x, y = self.get_panel_point(*self.edit_points[3])
cr.translate(x,y)
cr.rotate(math.radians(self.rotation))
cr.arc(0, 0, 6, math.radians(180), math.radians(-35))
cr.set_line_width(3.0)
cr.stroke()
cr.move_to(-6, 3)
cr.line_to(-9, 0)
cr.line_to(-3, 0)
cr.close_path()
cr.fill()
cr.arc(0, 0, 6, math.radians(0), math.radians(145))
cr.set_line_width(3.0)
cr.stroke()
cr.move_to(6, -3)
cr.line_to(9, 0)
cr.line_to(3, 0)
cr.close_path()
cr.fill()
def _draw_scale_arrow(self, cr, edit_point, add_angle):
cr.save()
x, y = self.get_panel_point(*edit_point)
cr.translate(x,y)
cr.rotate(math.radians(self.rotation + add_angle))
SHAFT_WIDTH = 2
SHAFT_LENGTH = 6
HEAD_WIDTH = 6
HEAD_LENGTH = 6
cr.move_to(0, - SHAFT_WIDTH)
cr.line_to(SHAFT_LENGTH, -SHAFT_WIDTH)
cr.line_to(SHAFT_LENGTH, -HEAD_WIDTH)
cr.line_to(SHAFT_LENGTH + HEAD_LENGTH, 0)
cr.line_to(SHAFT_LENGTH, HEAD_WIDTH)
cr.line_to(SHAFT_LENGTH, SHAFT_WIDTH)
cr.line_to(-SHAFT_LENGTH, SHAFT_WIDTH)
cr.line_to(-SHAFT_LENGTH, HEAD_WIDTH)
cr.line_to(-SHAFT_LENGTH - HEAD_LENGTH, 0)
cr.line_to(-SHAFT_LENGTH, -HEAD_WIDTH)
cr.line_to(-SHAFT_LENGTH, -SHAFT_WIDTH)
cr.close_path()
cr.set_source_rgb(1,1,1)
cr.fill_preserve()
cr.set_line_width(2.0)
cr.set_source_rgb(0,0,0)
cr.stroke()
cr.restore()
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/keyframeeditor.py 0000664 0000000 0000000 00000246156 13610327166 0026505 0 ustar 00root root 0000000 0000000 """
Flowblade Movie Editor is a nonlinear video editor.
Copyright 2012 Janne Liljeblad.
This file is part of Flowblade Movie Editor .
Flowblade Movie Editor 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.
Flowblade Movie Editor is distributed in the hope that 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 Flowblade Movie Editor. If not, see .
"""
"""
Module contains GUI widgets used to edit keyframed properties in filters and
compositors.
NOTE: All the editors are composites of smaller objects (so that similar
but slighly different editors can be made in the future). There are a lots
of callbacks to parent objects, this makes the design difficult to follow.
"""
import cairo
from gi.repository import Gtk, GObject
from gi.repository import Pango, PangoCairo
import cairoarea
import compositorfades
import editorpersistance
from editorstate import PLAYER
from editorstate import current_sequence
import gui
import guicomponents
import guiutils
import keyframeeditcanvas
import propertyedit
import propertyparse
import respaths
import utils
# Draw consts
CLIP_EDITOR_WIDTH = 250
CLIP_EDITOR_HEIGHT = 21
END_PAD = 28
TOP_PAD = 2
OUT_OF_RANGE_ICON_PAD = 20
OUT_OF_RANGE_KF_ICON_HALF = 6
OUT_OF_RANGE_NUMBER_Y = 5
OUT_OF_RANGE_NUMBER_X_START = 1
OUT_OF_RANGE_NUMBER_X_END_PAD = 7
BUTTON_WIDTH = 22
BUTTON_HEIGHT = 24
KF_Y = 5
CENTER_LINE_Y = 11
POS_ENTRY_W = 38
POS_ENTRY_H = 20
KF_HIT_WIDTH = 4
KF_DRAG_THRESHOLD = 3
GEOM_EDITOR_SIZE_LARGE = 0.9 # displayed screensize as fraction of available height
GEOM_EDITOR_SIZE_SMALL = 0.3 # displayed screensize as fraction of available height
GEOM_EDITOR_SIZE_MEDIUM = 0.6 # displayed screensize as fraction of available height
GEOM_EDITOR_SIZES = [GEOM_EDITOR_SIZE_LARGE, GEOM_EDITOR_SIZE_MEDIUM, GEOM_EDITOR_SIZE_SMALL]
# Colors
POINTER_COLOR = (1, 0.3, 0.3)
CLIP_EDITOR_BG_COLOR = (0.1445, 0.172, 0.25)
CLIP_EDITOR_NOT_ACTIVE_BG_COLOR = (0.25, 0.28, 0.34)
CLIP_EDITOR_CENTER_LINE_COLOR = (0.098, 0.313, 0.574)
LIGHT_MULTILPLIER = 1.14
DARK_MULTIPLIER = 0.74
# Editor states
KF_DRAG = 0
POSITION_DRAG = 1
KF_DRAG_DISABLED = 2
# Icons
ACTIVE_KF_ICON = None
NON_ACTIVE_KF_ICON = None
# Magic value to signify disconnected signal handler
DISCONNECTED_SIGNAL_HANDLER = -9999999
# Callbacks to compositeeditor.py, monkeypatched at startup
_get_current_edited_compositor = None
#add_fade_out_func = None
actions_menu = Gtk.Menu()
oor_before_menu = Gtk.Menu()
oor_after_menu = Gtk.Menu()
# ----------------------------------------------------- editor objects
class ClipKeyFrameEditor:
"""
GUI component used to add, move and remove keyframes
inside a single clip. It is used as a component inside a parent editor and
needs the parent editor to write out keyframe values.
Parent editor must implement callback interface:
def clip_editor_frame_changed(self, frame)
def active_keyframe_changed(self)
def keyframe_dragged(self, active_kf, frame)
def update_slider_value_display(self, frame)
def update_property_value(self)
"""
def __init__(self, editable_property, parent_editor, use_clip_in=True):
self.widget = cairoarea.CairoDrawableArea2( CLIP_EDITOR_WIDTH,
CLIP_EDITOR_HEIGHT,
self._draw)
self.widget.press_func = self._press_event
self.widget.motion_notify_func = self._motion_notify_event
self.widget.release_func = self._release_event
self.clip_length = editable_property.get_clip_length() - 1
self.sensitive = True
# Some filters start keyframes from *MEDIA* frame 0
# Some filters or compositors start keyframes from *CLIP* frame 0
# Filters starting from *MEDIA* 0 need offset
# to clip start added to all values.
#
# THIS IS NAMED A BIT UNCLEARLY: when use_clip_in=True this means that keyframes are relative to media
# and clip in is used as first frame of edit range, use_clip_in=False means that first frame of edit range is 0 and all keyframes are relative to that
#
self.use_clip_in = use_clip_in
if self.use_clip_in == True:
self.clip_in = editable_property.clip.clip_in
else:
self.clip_in = 0
self.current_clip_frame = self.clip_in
self.keyframes = [(0, 0.0)]
self.active_kf_index = 0
self.parent_editor = parent_editor
self.keyframe_parser = None # Function used to parse keyframes to tuples is different for different expressions
# Parent editor sets this.
self.current_mouse_action = None
self.drag_on = False # Used to stop updating pos here if pos change is initiated here.
self.drag_min = -1
self.drag_max = -1
self.mouse_listener = None #This is special service for RotoMaskKeyFrameEditor, not used by other editors
# init icons if needed
global ACTIVE_KF_ICON, NON_ACTIVE_KF_ICON
if ACTIVE_KF_ICON == None:
ACTIVE_KF_ICON = cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "kf_active.png")
if NON_ACTIVE_KF_ICON == None:
NON_ACTIVE_KF_ICON = cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "kf_not_active.png")
def set_keyframes(self, keyframes_str, out_to_in_func):
self.keyframes = self.keyframe_parser(keyframes_str, out_to_in_func)
def get_kf_info(self):
return (self.active_kf_index, len(self.keyframes))
def _get_panel_pos(self):
return self._get_panel_pos_for_frame(self.current_clip_frame)
def _get_panel_pos_for_frame(self, frame):
active_width = self.widget.get_allocation().width - 2 * END_PAD
disp_frame = frame - self.clip_in
return END_PAD + int((float(disp_frame) / float(self.clip_length)) *
active_width)
def _get_frame_for_panel_pos(self, panel_x):
active_width = self.widget.get_allocation().width - 2 * END_PAD
clip_panel_x = panel_x - END_PAD
norm_pos = float(clip_panel_x) / float(active_width)
return int(norm_pos * self.clip_length) + self.clip_in
def _set_clip_frame(self, panel_x):
self.current_clip_frame = self._get_frame_for_panel_pos(panel_x)
def move_clip_frame(self, delta):
self.current_clip_frame = self.current_clip_frame + delta
self._force_current_in_frame_range()
def set_and_display_clip_frame(self, clip_frame):
self.current_clip_frame = clip_frame
self._force_current_in_frame_range()
def _draw(self, event, cr, allocation):
"""
Callback for repaint from CairoDrawableArea.
We get cairo context and allocation.
"""
x, y, w, h = allocation
active_width = w - 2 * END_PAD
active_height = h - 2 * TOP_PAD
# Draw clip bg
cr.set_source_rgb(*CLIP_EDITOR_BG_COLOR)
if self.sensitive == False:
cr.set_source_rgb(*CLIP_EDITOR_NOT_ACTIVE_BG_COLOR)
cr.rectangle(END_PAD, TOP_PAD, active_width, active_height)
cr.fill()
# Clip edge and emboss
rect = (END_PAD, TOP_PAD, active_width, active_height)
self.draw_edge(cr, rect)
self.draw_emboss(cr, rect, gui.get_bg_color())
# Draw center line
cr.set_source_rgb(*CLIP_EDITOR_CENTER_LINE_COLOR)
cr.set_line_width(2.0)
cr.move_to(END_PAD, CENTER_LINE_Y)
cr.line_to(END_PAD + active_width, CENTER_LINE_Y)
cr.stroke()
# Draw keyframes
for i in range(0, len(self.keyframes)):
frame, value = self.keyframes[i]
if frame < self.clip_in:
continue
if frame > self.clip_in + self.clip_length:
continue
if i == self.active_kf_index:
icon = ACTIVE_KF_ICON
else:
icon = NON_ACTIVE_KF_ICON
try:
kf_pos = self._get_panel_pos_for_frame(frame)
except ZeroDivisionError: # math fails for 1 frame clip
kf_pos = END_PAD
cr.set_source_surface(icon, kf_pos - 6, KF_Y)
cr.paint()
# Draw out-of-range kf icons kf counts
before_kfs = len(self.get_out_of_range_before_kfs())
after_kfs = len(self.get_out_of_range_after_kfs())
if before_kfs > 0:
cr.set_source_surface(NON_ACTIVE_KF_ICON, OUT_OF_RANGE_ICON_PAD - OUT_OF_RANGE_KF_ICON_HALF * 2, KF_Y)
cr.paint()
self.draw_kf_count_number(cr, before_kfs, OUT_OF_RANGE_NUMBER_X_START, OUT_OF_RANGE_NUMBER_Y)
if after_kfs > 0:
cr.set_source_surface(NON_ACTIVE_KF_ICON, w - OUT_OF_RANGE_ICON_PAD, KF_Y)
cr.paint()
self.draw_kf_count_number(cr, after_kfs, w - OUT_OF_RANGE_NUMBER_X_END_PAD, OUT_OF_RANGE_NUMBER_Y)
# Draw frame pointer
try:
panel_pos = self._get_panel_pos()
except ZeroDivisionError: # math fails for 1 frame clip
panel_pos = END_PAD
cr.set_line_width(2.0)
cr.set_source_rgb(*POINTER_COLOR)
cr.move_to(panel_pos, 0)
cr.line_to(panel_pos, CLIP_EDITOR_HEIGHT)
cr.stroke()
def draw_emboss(self, cr, rect, color):
# Emboss, corner points
left = rect[0] + 1.5
up = rect[1] + 1.5
right = left + rect[2] - 2.0
down = up + rect[3] - 2.0
# Draw lines
color_tuple = gui.unpack_gdk_color(color)
light_color = guiutils.get_multiplied_color(color_tuple, LIGHT_MULTILPLIER)
cr.set_source_rgb(*light_color)
cr.move_to(left, down)
cr.line_to(left, up)
cr.stroke()
cr.move_to(left, up)
cr.line_to(right, up)
cr.stroke()
dark_color = guiutils.get_multiplied_color(color_tuple, DARK_MULTIPLIER)
cr.set_source_rgb(*dark_color)
cr.move_to(right, up)
cr.line_to(right, down)
cr.stroke()
cr.move_to(right, down)
cr.line_to(left, down)
cr.stroke()
def draw_edge(self, cr, rect):
cr.set_line_width(1.0)
cr.set_source_rgb(0, 0, 0)
cr.rectangle(rect[0] + 0.5, rect[1] + 0.5, rect[2], rect[3])
cr.stroke()
def draw_kf_count_number(self, cr, count, x, y):
# Draw track name
layout = PangoCairo.create_layout(cr)
layout.set_text(str(count), -1)
desc = Pango.FontDescription("Sans 8")
layout.set_font_description(desc)
cr.move_to(x, y)
cr.set_source_rgb(0.9, 0.9, 0.9)
PangoCairo.update_layout(cr, layout)
PangoCairo.show_layout(cr, layout)
def _press_event(self, event):
"""
Mouse button callback
"""
# Check if kf icon before or after clip range have been pressed
if self.oor_start_kf_hit(event.x, event.y):
self._show_oor_before_menu(self.widget, event)
return
if self.oor_end_kf_hit(event.x, event.y):
self._show_oor_after_menu(self.widget, event)
return
if self.sensitive == False:
return
# Handle clip range mouse events
self.drag_on = True
lx = self._legalize_x(event.x)
hit_kf = self._key_frame_hit(lx, event.y)
if hit_kf == None: # nothing was hit
self.current_mouse_action = POSITION_DRAG
self._set_clip_frame(lx)
self.parent_editor.clip_editor_frame_changed(self.current_clip_frame)
self.widget.queue_draw()
else: # some keyframe was pressed
self.active_kf_index = hit_kf
frame, value = self.keyframes[hit_kf]
self.current_clip_frame = frame
self.parent_editor.active_keyframe_changed()
if hit_kf == 0:
self.current_mouse_action = KF_DRAG_DISABLED
else:
self.current_mouse_action = KF_DRAG
self.drag_start_x = event.x
prev_frame, val = self.keyframes[hit_kf - 1]
self.drag_min = prev_frame + 1
try:
next_frame, val = self.keyframes[hit_kf + 1]
self.drag_max = next_frame - 1
except:
self.drag_max = self.clip_in + self.clip_length
self.widget.queue_draw()
def _motion_notify_event(self, x, y, state):
"""
Mouse move callback
"""
if self.sensitive == False:
return
lx = self._legalize_x(x)
if self.current_mouse_action == POSITION_DRAG:
self._set_clip_frame(lx)
self.parent_editor.clip_editor_frame_changed(self.current_clip_frame)
elif self.current_mouse_action == KF_DRAG:
frame = self._get_drag_frame(lx)
self.set_active_kf_frame(frame)
self.current_clip_frame = frame
self.parent_editor.keyframe_dragged(self.active_kf_index, frame)
self.parent_editor.active_keyframe_changed()
self.widget.queue_draw()
if self.mouse_listener != None:
self.mouse_listener.mouse_pos_change_done()
def _release_event(self, event):
"""
Mouse release callback.
"""
if self.sensitive == False:
return
lx = self._legalize_x(event.x)
if self.current_mouse_action == POSITION_DRAG:
self._set_clip_frame(lx)
self.parent_editor.clip_editor_frame_changed(self.current_clip_frame)
self.parent_editor.update_slider_value_display(self.current_clip_frame)
elif self.current_mouse_action == KF_DRAG:
frame = self._get_drag_frame(lx)
self.set_active_kf_frame(frame)
self.current_clip_frame = frame
self.parent_editor.keyframe_dragged(self.active_kf_index, frame)
self.parent_editor.active_keyframe_changed()
self.parent_editor.update_property_value()
self.parent_editor.update_slider_value_display(frame)
self.widget.queue_draw()
self.current_mouse_action = None
if self.mouse_listener != None:
self.mouse_listener.mouse_pos_change_done()
self.drag_on = False
def _legalize_x(self, x):
"""
Get x in pixel range between end pads.
"""
w = self.widget.get_allocation().width
if x < END_PAD:
return END_PAD
elif x > w - END_PAD:
return w - END_PAD
else:
return x
def _force_current_in_frame_range(self):
if self.current_clip_frame < self.clip_in:
self.current_clip_frame = self.clip_in
if self.current_clip_frame > self.clip_in + self.clip_length:
self.current_clip_frame = self.clip_in + self.clip_length
def get_out_of_range_before_kfs(self):
# returns Keyframes before current clip start
kfs = []
for i in range(0, len(self.keyframes)):
frame, value = self.keyframes[i]
if frame < self.clip_in:
kfs.append(self.keyframes[i])
return kfs
def get_out_of_range_after_kfs(self):
# returns Keyframes before current clip start
kfs = []
for i in range(0, len(self.keyframes)):
frame, value = self.keyframes[i]
if frame > self.clip_in + self.clip_length:
kfs.append(self.keyframes[i])
return kfs
def _get_drag_frame(self, panel_x):
"""
Get x in range available for current drag.
"""
frame = self._get_frame_for_panel_pos(panel_x)
if frame < self.drag_min:
frame = self.drag_min
if frame > self.drag_max:
frame = self.drag_max
return frame
def _key_frame_hit(self, x, y):
for i in range(0, len(self.keyframes)):
frame, val = self.keyframes[i]
frame_x = self._get_panel_pos_for_frame(frame)
frame_y = KF_Y + 6
if((abs(x - frame_x) < KF_HIT_WIDTH)
and (abs(y - frame_y) < KF_HIT_WIDTH)):
return i
return None
def oor_start_kf_hit(self, x, y):
test_x = OUT_OF_RANGE_ICON_PAD - OUT_OF_RANGE_KF_ICON_HALF * 2
if y >= KF_Y and y <= KF_Y + 12: # 12 icon size
if x >= test_x and x <= test_x + 12:
return True
return False
def oor_end_kf_hit(self, x, y):
w = self.widget.get_allocation().width
test_x = w - OUT_OF_RANGE_ICON_PAD
if y >= KF_Y and y <= KF_Y + 12: # 12 icon size
if x >= test_x and x <= test_x + 12:
return True
return False
def add_keyframe(self, frame):
# NOTE: This makes added keyframe the active keyframe too.
kf_index_on_frame = self.frame_has_keyframe(frame)
if kf_index_on_frame != -1:
# Trying add on top of existing keyframe makes it active
self.active_kf_index = kf_index_on_frame
return
for i in range(0, len(self.keyframes)):
kf_frame, kf_value = self.keyframes[i]
if kf_frame > frame:
prev_frame, prev_value = self.keyframes[i - 1]
self.keyframes.insert(i, (frame, prev_value))
self.active_kf_index = i
return
prev_frame, prev_value = self.keyframes[len(self.keyframes) - 1]
self.keyframes.append((frame, prev_value))
self.active_kf_index = len(self.keyframes) - 1
def print_keyframes(self, msg="no_msg"):
print(msg, "clip edit keyframes:")
for i in range(0, len(self.keyframes)):
print(self.keyframes[i])
def delete_active_keyframe(self):
if self.active_kf_index == 0:
# keyframe frame 0 cannot be removed
return
self.keyframes.pop(self.active_kf_index)
self.active_kf_index -= 1
if self.active_kf_index < 0:
self.active_kf_index = 0
self._set_pos_to_active_kf()
def set_next_active(self):
"""
Activates next keyframe or keeps last active to stay in range.
"""
self.active_kf_index += 1
if self.active_kf_index > (len(self.keyframes) - 1):
self.active_kf_index = len(self.keyframes) - 1
if self.active_kf_index > (len(self.keyframes) - 1 - len(self.get_out_of_range_after_kfs())):
self.active_kf_index = (len(self.keyframes) - 1 - len(self.get_out_of_range_after_kfs()))
self._set_pos_to_active_kf()
def set_prev_active(self):
"""
Activates previous keyframe or keeps first active to stay in range.
"""
self.active_kf_index -= 1
if self.active_kf_index < 0:
self.active_kf_index = 0
if self.active_kf_index < len(self.get_out_of_range_before_kfs()):
self.active_kf_index = len(self.get_out_of_range_before_kfs())
self._set_pos_to_active_kf()
def _set_pos_to_active_kf(self):
try:
frame, value = self.keyframes[self.active_kf_index]
self.current_clip_frame = frame
self._force_current_in_frame_range()
self.parent_editor.update_slider_value_display(self.current_clip_frame)
except:
pass # This can fail if no keyframes exist in edit range but then we will just do nothing
def frame_has_keyframe(self, frame):
"""
Returns index of keyframe if frame has keyframe or -1 if it doesn't.
"""
for i in range(0, len(self.keyframes)):
kf_frame, kf_value = self.keyframes[i]
if frame == kf_frame:
return i
return -1
def get_active_kf_frame(self):
frame, val = self.keyframes[self.active_kf_index]
return frame
def get_active_kf_value(self):
frame, val = self.keyframes[self.active_kf_index]
return val
def set_active_kf_value(self, new_value):
frame, val = self.keyframes.pop(self.active_kf_index)
self.keyframes.insert(self.active_kf_index,(frame, new_value))
def active_kf_pos_entered(self, frame):
if self.active_kf_index == 0:
return
prev_frame, val = self.keyframes[self.active_kf_index - 1]
prev_frame += 1
try:
next_frame, val = self.keyframes[self.active_kf_index + 1]
next_frame -= 1
except:
next_frame = self.clip_in + self.clip_length
frame = max(frame, prev_frame)
frame = min(frame, next_frame)
self.set_active_kf_frame(frame)
self.current_clip_frame = frame
def maybe_set_first_kf_in_clip_area_active(self):
index = 0
for kf in self.keyframes:
kf_frame, val = kf
if kf_frame >= self.clip_in:
self.active_kf_index = index
self._set_pos_to_active_kf()
break
index += 1
def set_active_kf_frame(self, new_frame):
frame, val = self.keyframes.pop(self.active_kf_index)
self.keyframes.insert(self.active_kf_index,(new_frame, val))
def _show_oor_before_menu(self, widget, event):
menu = oor_before_menu
guiutils.remove_children(menu)
before_kfs = len(self.get_out_of_range_before_kfs())
if before_kfs == 0:
# hit detection is active even if the kf icon is not displayed
return
if before_kfs > 1:
menu.add(self._get_menu_item(_("Delete all but first Keyframe before Clip Range"), self._oor_menu_item_activated, "delete_all_before" ))
sep = Gtk.SeparatorMenuItem()
sep.show()
menu.add(sep)
if len(self.keyframes) > 1:
menu.add(self._get_menu_item(_("Set Keyframe at Frame 0 to value of next Keyframe"), self._oor_menu_item_activated, "zero_next" ))
elif before_kfs == 1:
item = self._get_menu_item(_("No Edit Actions currently available"), self._oor_menu_item_activated, "noop" )
item.set_sensitive(False)
menu.add(item)
menu.popup(None, None, None, None, event.button, event.time)
def _show_oor_after_menu(self, widget, event):
menu = oor_before_menu
guiutils.remove_children(menu)
after_kfs = self.get_out_of_range_after_kfs()
if after_kfs == 0:
# hit detection is active even if the kf icon is not displayed
return
menu.add(self._get_menu_item(_("Delete all Keyframes after Clip Range"), self._oor_menu_item_activated, "delete_all_after" ))
menu.popup(None, None, None, None, event.button, event.time)
def _oor_menu_item_activated(self, widget, data):
if data == "delete_all_before":
keep_doing = True
while keep_doing:
try:
frame, value = self.keyframes[1]
if frame < self.clip_in:
self.keyframes.pop(1)
else:
keep_doing = False
except:
keep_doing = False
elif data == "zero_next":
frame_zero, frame_zero_value = self.keyframes[0]
frame, value = self.keyframes[1]
self.keyframes.pop(0)
self.keyframes.insert(0, (frame_zero, value))
self.parent_editor.update_property_value()
elif data == "delete_all_after":
delete_done = False
for i in range(0, len(self.keyframes)):
frame, value = self.keyframes[i]
if frame > self.clip_in + self.clip_length:
self.keyframes.pop(i)
popped = True
while popped:
try:
self.keyframes.pop(i)
except:
popped = False
delete_done = True
if delete_done:
break
self.widget.queue_draw()
def _get_menu_item(self, text, callback, data):
item = Gtk.MenuItem(text)
item.connect("activate", callback, data)
item.show()
return item
def set_sensitive(self, sensitive):
self.sensitive = sensitive
# ----------------------------------------------------------- buttons objects
class ClipEditorButtonsRow(Gtk.HBox):
"""
Row of buttons used to navigate and add keyframes and frame
entry box for active keyframe. Parent editor must implemnt interface
defined by methods:
editor_parent.add_pressed()
editor_parent.delete_pressed()
editor_parent.prev_pressed()
editor_parent.next_pressed()
editor_parent.prev_frame_pressed()
editor_parent.next_frame_pressed()
"""
def __init__(self, editor_parent, centered_buttons=False, show_fade_buttons=True):
GObject.GObject.__init__(self)
self.set_homogeneous(False)
self.set_spacing(2)
# Aug-2019 - SvdB - BB
self.add_button = guiutils.get_image_button("add_kf", BUTTON_WIDTH, BUTTON_HEIGHT)
self.delete_button = guiutils.get_image_button("delete_kf", BUTTON_WIDTH, BUTTON_HEIGHT)
self.prev_kf_button = guiutils.get_image_button("prev_kf", BUTTON_WIDTH, BUTTON_HEIGHT)
self.next_kf_button = guiutils.get_image_button("next_kf", BUTTON_WIDTH, BUTTON_HEIGHT)
self.prev_frame_button = guiutils.get_image_button("kf_edit_prev_frame", BUTTON_WIDTH, BUTTON_HEIGHT)
self.next_frame_button = guiutils.get_image_button("kf_edit_next_frame", BUTTON_WIDTH, BUTTON_HEIGHT)
self.kf_to_prev_frame_button = guiutils.get_image_button("kf_edit_kf_to_prev_frame", BUTTON_WIDTH, BUTTON_HEIGHT)
self.kf_to_next_frame_button = guiutils.get_image_button("kf_edit_kf_to_next_frame", BUTTON_WIDTH, BUTTON_HEIGHT)
self.add_fade_in_button = guiutils.get_image_button("add_fade_in", BUTTON_WIDTH, BUTTON_HEIGHT)
self.add_fade_out_button = guiutils.get_image_button("add_fade_out", BUTTON_WIDTH, BUTTON_HEIGHT)
self.add_button.connect("clicked", lambda w,e: editor_parent.add_pressed(), None)
self.delete_button.connect("clicked", lambda w,e: editor_parent.delete_pressed(), None)
self.prev_kf_button.connect("clicked", lambda w,e: editor_parent.prev_pressed(), None)
self.next_kf_button.connect("clicked", lambda w,e: editor_parent.next_pressed(), None)
self.prev_frame_button.connect("clicked", lambda w,e: editor_parent.prev_frame_pressed(), None)
self.next_frame_button.connect("clicked", lambda w,e: editor_parent.next_frame_pressed(), None)
self.kf_to_prev_frame_button.connect("clicked", lambda w,e: editor_parent.move_kf_prev_frame_pressed(), None)
self.kf_to_next_frame_button.connect("clicked", lambda w,e: editor_parent.move_kf_next_frame_pressed(), None)
self.add_fade_in_button.connect("clicked", lambda w,e: editor_parent.add_fade_in(), None)
self.add_fade_out_button.connect("clicked", lambda w,e: editor_parent.add_fade_out(), None)
self.add_button.set_tooltip_text(_("Add Keyframe"))
self.delete_button.set_tooltip_text(_("Delete Keyframe"))
self.prev_kf_button.set_tooltip_text(_("Previous Keyframe"))
self.next_kf_button.set_tooltip_text(_("Next Keyframe"))
self.prev_frame_button.set_tooltip_text(_("Previous Frame"))
self.next_frame_button.set_tooltip_text(_("Next Frame"))
self.kf_to_prev_frame_button.set_tooltip_text(_("Move Keyframe 1 Frame Back"))
self.kf_to_next_frame_button.set_tooltip_text(_("Move Keyframe 1 Frame Forward"))
self.add_fade_in_button.set_tooltip_text(_("Add Fade In"))
self.add_fade_out_button.set_tooltip_text(_("Add Fade Out"))
# Position entry
self.kf_pos_label = Gtk.Label()
self.modify_font(Pango.FontDescription("light 8"))
self.kf_pos_label.set_text("0")
self.kf_info_label = Gtk.Label()
self.kf_info_label.set_text("1/1")
# Build row
if centered_buttons:
self.pack_start(Gtk.Label(), True, True, 0)
self.pack_start(self.add_button, False, False, 0)
self.pack_start(self.delete_button, False, False, 0)
self.pack_start(guiutils.pad_label(24,4), False, False, 0)
self.pack_start(self.prev_kf_button, False, False, 0)
self.pack_start(self.next_kf_button, False, False, 0)
self.pack_start(self.kf_to_prev_frame_button, False, False, 0)
self.pack_start(self.kf_to_next_frame_button, False, False, 0)
self.pack_start(self.prev_frame_button, False, False, 0)
self.pack_start(self.next_frame_button, False, False, 0)
self.pack_start(guiutils.pad_label(24,4), False, False, 0)
if show_fade_buttons:
self.pack_start(self.add_fade_in_button, False, False, 0)
self.pack_start(self.add_fade_out_button, False, False, 0)
if not centered_buttons:
self.pack_start(Gtk.Label(), True, True, 0)
else:
self.pack_start(guiutils.pad_label(4,4), False, False, 0)
self.pack_start(self.kf_info_label, False, False, 0)
self.pack_start(guiutils.pad_label(24,4), False, False, 0)
self.pack_start(self.kf_pos_label, False, False, 0)
if centered_buttons:
self.pack_start(Gtk.Label(), True, True, 0)
else:
self.pack_start(guiutils.get_pad_label(1, 10), False, False, 0)
def set_frame(self, frame):
frame_str = utils.get_tc_string(frame)
self.kf_pos_label.set_text(frame_str)
def set_kf_info(self, info):
active_index, total = info
self.kf_info_label.set_text(str(active_index + 1) + "/" + str(total))
def set_buttons_sensitive(self, sensitive):
self.add_button.set_sensitive(sensitive)
self.delete_button.set_sensitive(sensitive)
self.prev_kf_button.set_sensitive(sensitive)
self.next_kf_button.set_sensitive(sensitive)
self.prev_frame_button.set_sensitive(sensitive)
self.next_frame_button.set_sensitive(sensitive)
self.kf_to_prev_frame_button.set_sensitive(sensitive)
self.kf_to_next_frame_button.set_sensitive(sensitive)
class GeometryEditorButtonsRow(Gtk.HBox):
def __init__(self, editor_parent, empty_center=False):
"""
editor_parent needs to implement interface:
-------------------------------------------
editor_parent.view_size_changed(widget_active_index)
editor_parent.menu_item_activated()
"""
GObject.GObject.__init__(self)
self.set_homogeneous(False)
self.set_spacing(2)
self.editor_parent = editor_parent
name_label = Gtk.Label(label=_("View:"))
# Aug-2019 - SvdB - BB
size_adj = 1
if editorpersistance.prefs.double_track_hights:
size_adj = 2
surface = guiutils.get_cairo_image("geom_action")
action_menu_button = guicomponents.PressLaunch(self._show_actions_menu, surface, 24*size_adj, 22*size_adj)
size_select = Gtk.ComboBoxText()
size_select.append_text("100%")
size_select.append_text("66%")
size_select.append_text("33%")
size_select.set_active(1)
size_select.connect("changed", lambda w,e: editor_parent.view_size_changed(w.get_active()),
None)
self.size_select = size_select
# Build row
self.pack_start(action_menu_button.widget, False, False, 0)
if empty_center == True:
self.pack_start(Gtk.Label(), True, True, 0)
else:
self.pack_start(guiutils.get_pad_label(12, 10), False, False, 0)
self.pack_start(size_select, False, False, 0)
def _show_actions_menu(self, widget, event):
menu = actions_menu
guiutils.remove_children(menu)
menu.add(self._get_menu_item(_("Reset Geometry"), self.editor_parent.menu_item_activated, "reset" ))
menu.add(self._get_menu_item(_("Geometry to Original Aspect Ratio"), self.editor_parent.menu_item_activated, "ratio" ))
menu.add(self._get_menu_item(_("Center Horizontal"), self.editor_parent.menu_item_activated, "hcenter" ))
menu.add(self._get_menu_item(_("Center Vertical"), self.editor_parent.menu_item_activated, "vcenter" ))
menu.popup(None, None, None, None, event.button, event.time)
def _get_menu_item(self, text, callback, data):
item = Gtk.MenuItem(text)
item.connect("activate", callback, data)
item.show()
return item
# ------------------------------------------------------------ master editors
class AbstractKeyFrameEditor(Gtk.VBox):
"""
AbstractKeyFrameEditor is parent editor for ClipKeyFrameEditor and is updated with callbacks
from there for timeline position changes keyframe changes. Extending classes KeyframeEditor and GeometryEditor
handles some of the ClipKeyFrameEditor callbacks.
AbstractKeyFrameEditor editor also has slider for setting keyframe values.
"""
def __init__(self, editable_property, use_clip_in=True, slider_switcher=None):
# editable_property is KeyFrameProperty
GObject.GObject.__init__(self)
self.initializing = True # Hack against too early for on slider listner
self.set_homogeneous(False)
self.set_spacing(2)
self.editable_property = editable_property
self.clip_tline_pos = editable_property.get_clip_tline_pos()
self.clip_editor = ClipKeyFrameEditor(editable_property, self, use_clip_in)
"""
Callbacks from ClipKeyFrameEditor:
def clip_editor_frame_changed(self, frame)
def active_keyframe_changed(self)
def keyframe_dragged(self, active_kf, frame)
def update_slider_value_display(self, frame)
These may be implemented here or in extending classes KeyframeEditor and GeometryEditor
"""
# Some filters start keyframes from *MEDIA* frame 0
# Some filters or compositors start keyframes from *CLIP* frame 0
# Filters starting from *media* 0 need offset to clip start added to all values
self.use_clip_in = use_clip_in
if self.use_clip_in == True:
self.clip_in = editable_property.clip.clip_in
else:
self.clip_in = 0
# Value slider
row, slider, spin = guiutils.get_slider_row_and_spin_widget(editable_property, self.slider_value_changed)
if slider_switcher != None:
hbox = Gtk.HBox(False, 4)
hbox.pack_start(row, True, True, 0)
hbox.pack_start(slider_switcher.widget, False, False, 4)
row = hbox
self.value_slider_row = row
self.slider = slider
self.spin = spin
self.initializing = False # Hack against too early for on slider listner
def display_tline_frame(self, tline_frame):
# This is called after timeline current frame changed.
# If timeline pos changed because drag is happening _here_,
# updating once more is wrong
if self.clip_editor.drag_on == True:
return
# update clipeditor pos
clip_frame = tline_frame - self.clip_tline_pos + self.clip_in
self.clip_editor.set_and_display_clip_frame(clip_frame)
self.update_editor_view(False)
def update_clip_pos(self):
# This is called after position of clip has been edited.
# We'll need to update some values to get keyframes on correct positions again
self.editable_property.update_clip_index()
self.clip_tline_pos = self.editable_property.get_clip_tline_pos()
if self.use_clip_in == True:
self.clip_in = self.editable_property.clip.clip_in
else:
self.clip_in = 0
self.clip_editor.clip_in = self.editable_property.clip.clip_in
self.clip_editor.widget.queue_draw()
def update_slider_value_display(self, frame):
# This is called after frame changed or mouse release to update
# slider value without causing 'changed' signal to update keyframes.
if self.editable_property.value_changed_ID != DISCONNECTED_SIGNAL_HANDLER:
self.slider.get_adjustment().handler_block(self.editable_property.value_changed_ID)
new_value = _get_frame_value(frame, self.clip_editor.keyframes)
self.editable_property.adjustment.set_value(new_value)
if self.editable_property.value_changed_ID != DISCONNECTED_SIGNAL_HANDLER:
self.slider.get_adjustment().handler_unblock(self.editable_property.value_changed_ID)
def seek_tline_frame(self, clip_frame):
PLAYER().seek_frame(self.clip_tline_pos + clip_frame - self.clip_in)
def update_editor_view(self, seek_tline=True):
print(type(self), "update_editor_view not implemented")
def paste_kf_value(self, value):
print(type(self), "paste_kf_value not implemented")
def get_copy_kf_value(self):
print(type(self), "get_copy_kf_value not implemented")
class KeyFrameEditor(AbstractKeyFrameEditor):
"""
Class combines named value slider with ClipKeyFrameEditor and
control buttons to create keyframe editor for a single keyframed
numerical value property.
"""
def __init__(self, editable_property, use_clip_in=True, slider_switcher=None, fade_buttons=False):
AbstractKeyFrameEditor.__init__(self, editable_property, use_clip_in, slider_switcher)
self.slider_switcher = slider_switcher
# default parser
self.clip_editor.keyframe_parser = propertyparse.single_value_keyframes_string_to_kf_array
# parsers for other editable_property types
if isinstance(editable_property, propertyedit.OpacityInGeomKeyframeProperty):
self.clip_editor.keyframe_parser = propertyparse.geom_keyframes_value_string_to_opacity_kf_array
editable_property.value.strip('"')
self.clip_editor.set_keyframes(editable_property.value, editable_property.get_in_value)
clip_editor_row = Gtk.HBox(False, 0)
clip_editor_row.pack_start(self.clip_editor.widget, True, True, 0)
clip_editor_row.pack_start(guiutils.pad_label(4, 4), False, False, 0)
self.buttons_row = ClipEditorButtonsRow(self, False, fade_buttons)
self.pack_start(self.value_slider_row, False, False, 0)
self.pack_start(clip_editor_row, False, False, 0)
self.pack_start(self.buttons_row, False, False, 0)
orig_tline_frame = PLAYER().current_frame()
self.active_keyframe_changed() # to do update gui to current values
# This also seeks tline frame to frame 0, thus value was saved in the line above
# If we do not want to seek to kf 0 or clip start we, need seek back to original tline frame
self.display_tline_frame(orig_tline_frame)
PLAYER().seek_frame(orig_tline_frame)
def slider_value_changed(self, adjustment):
value = adjustment.get_value()
# Add key frame if were not on active key frame
active_kf_frame = self.clip_editor.get_active_kf_frame()
current_frame = self.clip_editor.current_clip_frame
if current_frame != active_kf_frame:
self.clip_editor.add_keyframe(current_frame)
self.clip_editor.set_active_kf_value(value)
self.update_editor_view()
self.update_property_value()
else: # if on kf, just update value
self.clip_editor.set_active_kf_value(value)
self.update_property_value()
def active_keyframe_changed(self):
frame = self.clip_editor.current_clip_frame
keyframes = self.clip_editor.keyframes
value = _get_frame_value(frame, keyframes)
self.slider.set_value(value)
self.buttons_row.set_frame(frame)
self.seek_tline_frame(frame)
self.buttons_row.set_kf_info(self.clip_editor.get_kf_info())
def clip_editor_frame_changed(self, clip_frame):
self.seek_tline_frame(clip_frame)
self.buttons_row.set_frame(clip_frame)
def add_pressed(self):
self.clip_editor.add_keyframe(self.clip_editor.current_clip_frame)
self.update_editor_view()
self.update_property_value()
self.buttons_row.set_kf_info(self.clip_editor.get_kf_info())
def delete_pressed(self):
self.clip_editor.delete_active_keyframe()
self.update_editor_view()
self.update_property_value()
self.buttons_row.set_kf_info(self.clip_editor.get_kf_info())
def get_copy_kf_value(self):
return self.clip_editor.get_active_kf_value()
def paste_kf_value(self, value_data):
self.clip_editor.set_active_kf_value(value_data)
self.update_editor_view()
self.update_property_value()
def next_pressed(self):
self.clip_editor.set_next_active()
self.update_editor_view()
self.buttons_row.set_kf_info(self.clip_editor.get_kf_info())
def prev_pressed(self):
self.clip_editor.set_prev_active()
self.update_editor_view()
self.buttons_row.set_kf_info(self.clip_editor.get_kf_info())
def prev_frame_pressed(self):
self.clip_editor.move_clip_frame(-1)
self.update_editor_view()
self.buttons_row.set_kf_info(self.clip_editor.get_kf_info())
def next_frame_pressed(self):
self.clip_editor.move_clip_frame(1)
self.update_editor_view()
def move_kf_next_frame_pressed(self):
current_frame = self.clip_editor.get_active_kf_frame()
self.clip_editor.active_kf_pos_entered(current_frame + 1)
self.update_property_value()
self.update_editor_view()
def move_kf_prev_frame_pressed(self):
current_frame = self.clip_editor.get_active_kf_frame()
self.clip_editor.active_kf_pos_entered(current_frame - 1)
self.update_property_value()
self.update_editor_view()
def keyframe_dragged(self, active_kf, frame):
pass
def update_editor_view(self, seek_tline=True):
frame = self.clip_editor.current_clip_frame
keyframes = self.clip_editor.keyframes
value = _get_frame_value(frame, keyframes)
self.buttons_row.set_frame(frame)
if seek_tline == True:
self.seek_tline_frame(frame)
self.update_slider_value_display(frame) # NOTE!!!!!!!!!! Added for 2.0, observe if adds crashes
self.queue_draw()
def connect_to_update_on_release(self):
self.editable_property.adjustment.disconnect(self.editable_property.value_changed_ID)
self.editable_property.value_changed_ID = DISCONNECTED_SIGNAL_HANDLER
self.spin.connect("activate", lambda w:self.spin_value_changed(w))
self.spin.connect("button-release-event", lambda w, e:self.spin_value_changed(w))
self.slider.connect("button-release-event", lambda w, e:self.slider_value_changed(w.get_adjustment()))
def update_property_value(self):
self.editable_property.write_out_keyframes(self.clip_editor.keyframes)
def spin_value_changed(self, w):
adj = w.get_adjustment()
val = int(w.get_text())
adj.set_value(float(val))
self.slider_value_changed(adj)
class KeyFrameEditorClipFade(KeyFrameEditor):
"""
Used for compositors with just slider and keyframes.
"""
def __init__(self, editable_property):
KeyFrameEditor.__init__(self, editable_property, use_clip_in=False, slider_switcher=None, fade_buttons=True)
def add_fade_in(self):
compositor = _get_current_edited_compositor()
keyframes = compositorfades.add_fade_in(compositor, 10) # updates editable_property.value.
if keyframes == None:
return # update failed, clip probably too short
self._update_all_for_kf_vec(keyframes)
def add_fade_out(self):
compositor = _get_current_edited_compositor()
keyframes = compositorfades.add_fade_out(compositor, 10) # updates editable_property.value.
if keyframes == None:
return # update failed, clip probably too short
self._update_all_for_kf_vec(keyframes)
def _update_all_for_kf_vec(self, keyframes):
self.editable_property.write_out_keyframes(keyframes)
self.clip_editor.set_keyframes(self.editable_property.value, self.editable_property.get_in_value)
self.update_editor_view()
class GeometryEditor(AbstractKeyFrameEditor):
"""
GUI component that edits position, scale and opacity of a MLT property.
"""
def __init__(self, editable_property, use_clip_in=True):
AbstractKeyFrameEditor.__init__(self, editable_property, use_clip_in)
self.init_geom_gui(editable_property)
self.init_non_geom_gui()
def init_geom_gui(self, editable_property):
self.geom_kf_edit = keyframeeditcanvas.BoxEditCanvas(editable_property, self)
self.geom_kf_edit.init_editor(current_sequence().profile.width(),
current_sequence().profile.height(),
GEOM_EDITOR_SIZE_MEDIUM)
editable_property.value.strip('"')
self.geom_kf_edit.keyframe_parser = propertyparse.geom_keyframes_value_string_to_geom_kf_array
self.geom_kf_edit.set_keyframes(editable_property.value, editable_property.get_in_value)
def init_non_geom_gui(self):
# Create components
self.geom_buttons_row = GeometryEditorButtonsRow(self)
g_frame = Gtk.Frame()
g_frame.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
g_frame.add(self.geom_kf_edit.widget)
self.buttons_row = ClipEditorButtonsRow(self, False, True)
self.pos_entries_row = PositionNumericalEntries(self.geom_kf_edit, self, self.geom_buttons_row)
# Create clip editor keyframes from geom editor keyframes
# that contain the property values when opening editor.
# From now on clip editor opacity values are used until editor is discarded.
self.clip_editor.keyframes = self.get_clip_editor_keyframes()
# Build gui
#self.pack_start(self.geom_buttons_row, False, False, 0)
self.pack_start(g_frame, False, False, 0)
self.pack_start(self.pos_entries_row, False, False, 0)
self.pack_start(guiutils.pad_label(1, 1), False, False, 0)
self.pack_start(self.value_slider_row, False, False, 0)
self.pack_start(self.clip_editor.widget, False, False, 0)
self.pack_start(self.buttons_row, False, False, 0)
orig_tline_frame = PLAYER().current_frame()
self.active_keyframe_changed() # to do update gui to current values
# This also seeks tline frame to frame 0, thus value was saved in the line above
# If we do not want to seek to kf 0 or clip start we, need seek back to original tline frame
self.display_tline_frame(orig_tline_frame)
PLAYER().seek_frame(orig_tline_frame)
self.queue_draw()
def get_clip_editor_keyframes(self):
keyframes = []
for kf in self.geom_kf_edit.keyframes:
frame, rect, opacity = kf
clip_kf = (frame, opacity)
keyframes.append(clip_kf)
return keyframes
def add_pressed(self):
# These two have different keyframe, clip_editor only deals with opacity.
# This because clip_editor is the same class used to keyframe edit single values
self.clip_editor.add_keyframe(self.clip_editor.current_clip_frame)
self.geom_kf_edit.add_keyframe(self.clip_editor.current_clip_frame)
frame = self.clip_editor.get_active_kf_frame()
self.pos_entries_row.update_entry_values(self.geom_kf_edit.get_keyframe(self.clip_editor.active_kf_index))
self.update_editor_view_with_frame(frame)
self.update_property_value()
self.buttons_row.set_kf_info(self.clip_editor.get_kf_info())
def delete_pressed(self):
active = self.clip_editor.active_kf_index
self.clip_editor.delete_active_keyframe()
self.geom_kf_edit.delete_active_keyframe(active)
frame = self.clip_editor.get_active_kf_frame()
self.pos_entries_row.update_entry_values(self.geom_kf_edit.get_keyframe(self.clip_editor.active_kf_index))
self.update_editor_view_with_frame(frame)
self.update_property_value()
self.buttons_row.set_kf_info(self.clip_editor.get_kf_info())
def next_pressed(self):
self.clip_editor.set_next_active()
frame = self.clip_editor.get_active_kf_frame()
self.update_editor_view_with_frame(frame)
self.buttons_row.set_kf_info(self.clip_editor.get_kf_info())
self.pos_entries_row.update_entry_values(self.geom_kf_edit.get_keyframe(self.clip_editor.active_kf_index))
def prev_pressed(self):
self.clip_editor.set_prev_active()
frame = self.clip_editor.get_active_kf_frame()
self.update_editor_view_with_frame(frame)
self.buttons_row.set_kf_info(self.clip_editor.get_kf_info())
self.pos_entries_row.update_entry_values(self.geom_kf_edit.get_keyframe(self.clip_editor.active_kf_index))
def move_kf_next_frame_pressed(self):
current_frame = self.clip_editor.get_active_kf_frame()
self.clip_editor.active_kf_pos_entered(current_frame + 1)
current_frame = self.clip_editor.get_active_kf_frame()
self.update_property_value()
self.update_editor_view()
self.seek_tline_frame(current_frame)
def move_kf_prev_frame_pressed(self):
current_frame = self.clip_editor.get_active_kf_frame()
self.clip_editor.active_kf_pos_entered(current_frame - 1)
current_frame = self.clip_editor.get_active_kf_frame()
self.update_property_value()
self.update_editor_view()
self.seek_tline_frame(current_frame)
def slider_value_changed(self, adjustment):
value = adjustment.get_value()
self.clip_editor.set_active_kf_value(value)
self.update_property_value()
def get_copy_kf_value(self):
return self.geom_kf_edit.get_keyframe(self.clip_editor.active_kf_index)
def paste_kf_value(self, value_data):
frame, rect, opacity = value_data
self.clip_editor.set_active_kf_value(opacity)
self.geom_kf_edit.set_keyframe_to_edit_shape(self.clip_editor.active_kf_index, rect)
self.update_property_value()
self.update_editor_view()
def add_fade_in(self):
compositor = _get_current_edited_compositor()
keyframes = compositorfades.add_fade_in(compositor, 10) # updates editable_property.value. Remove fade length hardcoding in 2.4
if keyframes == None:
return # update failed, clip probably too short
self._update_all_for_kf_vec(keyframes)
def add_fade_out(self):
compositor = _get_current_edited_compositor()
keyframes = compositorfades.add_fade_out(compositor, 10) # updates editable_property.value. Remove fade length hardcoding in 2.4
if keyframes == None:
return # update failed, clip probably too short
self._update_all_for_kf_vec(keyframes)
def _update_all_for_kf_vec(self, keyframes):
self.editable_property.write_out_keyframes(keyframes)
self.geom_kf_edit.set_keyframes(self.editable_property.value, self.editable_property.get_in_value)
self.clip_editor.keyframes = self.get_clip_editor_keyframes()
self.clip_editor.widget.queue_draw()
self.update_editor_view()
def view_size_changed(self, selected_index):
y_fract = GEOM_EDITOR_SIZES[selected_index]
self.geom_kf_edit.set_view_size(y_fract)
self.update_editor_view_with_frame(self.clip_editor.current_clip_frame)
def clip_editor_frame_changed(self, frame):
self.update_editor_view_with_frame(frame)
def prev_frame_pressed(self):
self.clip_editor.move_clip_frame(-1)
self.update_editor_view(True)
def next_frame_pressed(self):
self.clip_editor.move_clip_frame(1)
self.update_editor_view(True)
def geometry_edit_started(self): # callback from geom_kf_edit
self.clip_editor.add_keyframe(self.clip_editor.current_clip_frame)
self.geom_kf_edit.add_keyframe(self.clip_editor.current_clip_frame)
def geometry_edit_finished(self): # callback from geom_kf_edit
self.geom_kf_edit.set_keyframe_to_edit_shape(self.clip_editor.active_kf_index)
self.update_editor_view_with_frame(self.clip_editor.current_clip_frame)
self.update_property_value()
self.buttons_row.set_kf_info(self.clip_editor.get_kf_info())
self.pos_entries_row.update_entry_values(self.geom_kf_edit.get_keyframe(self.clip_editor.active_kf_index))
def numerical_edit_done(self, new_shape):
# Callback from PositionNumericalEntries
self.geom_kf_edit.set_keyframe_to_edit_shape(self.clip_editor.active_kf_index, new_shape)
self.update_editor_view_with_frame(self.clip_editor.current_clip_frame)
self.update_property_value()
self.buttons_row.set_kf_info(self.clip_editor.get_kf_info())
self.pos_entries_row.update_entry_values(self.geom_kf_edit.get_keyframe(self.clip_editor.active_kf_index))
def arrow_edit(self, keyval, CTRL_DOWN, SHIFT_DOWN):
if CTRL_DOWN:
delta = 10
else:
delta = 1
if SHIFT_DOWN == False: # Move
self.geom_kf_edit.handle_arrow_edit(keyval, delta)
else: # Scale
self.geom_kf_edit.handle_arrow_scale_edit(keyval, delta)
self.geom_kf_edit.set_keyframe_to_edit_shape(self.clip_editor.active_kf_index)
self.update_editor_view_with_frame(self.clip_editor.current_clip_frame)
self.update_property_value()
def update_request_from_geom_editor(self): # callback from geom_kf_edit
self.update_editor_view_with_frame(self.clip_editor.current_clip_frame)
self.pos_entries_row.update_entry_values(self.geom_kf_edit.get_keyframe(self.clip_editor.active_kf_index))
def keyframe_dragged(self, active_kf, frame):
self.geom_kf_edit.set_keyframe_frame(active_kf, frame)
def active_keyframe_changed(self): # callback from clip_editor
kf_frame = self.clip_editor.get_active_kf_frame()
self.update_editor_view_with_frame(kf_frame)
self.buttons_row.set_kf_info(self.clip_editor.get_kf_info())
# we need active index from clip_editor and geometry values from geom_kf_edit to update numerical entries
self.pos_entries_row.update_entry_values(self.geom_kf_edit.get_keyframe(self.clip_editor.active_kf_index))
def _reset_rect_pressed(self):
self.geom_kf_edit.reset_active_keyframe_shape(self.clip_editor.active_kf_index)
self.pos_entries_row.update_entry_values(self.geom_kf_edit.get_keyframe(self.clip_editor.active_kf_index))
frame = self.clip_editor.get_active_kf_frame()
self.update_editor_view_with_frame(frame)
self.update_property_value()
def _reset_rect_ratio_pressed(self):
self.geom_kf_edit.reset_active_keyframe_rect_shape(self.clip_editor.active_kf_index)
frame = self.clip_editor.get_active_kf_frame()
self.pos_entries_row.update_entry_values(self.geom_kf_edit.get_keyframe(self.clip_editor.active_kf_index))
self.update_editor_view_with_frame(frame)
self.update_property_value()
def _center_horizontal(self):
self.geom_kf_edit.center_h_active_keyframe_shape(self.clip_editor.active_kf_index)
frame = self.clip_editor.get_active_kf_frame()
self.pos_entries_row.update_entry_values(self.geom_kf_edit.get_keyframe(self.clip_editor.active_kf_index))
self.update_editor_view_with_frame(frame)
self.update_property_value()
def _center_vertical(self):
self.geom_kf_edit.center_v_active_keyframe_shape(self.clip_editor.active_kf_index)
frame = self.clip_editor.get_active_kf_frame()
self.pos_entries_row.update_entry_values(self.geom_kf_edit.get_keyframe(self.clip_editor.active_kf_index))
self.update_editor_view_with_frame(frame)
self.update_property_value()
def menu_item_activated(self, widget, data):
if data == "reset":
self._reset_rect_pressed()
elif data == "ratio":
self._reset_rect_ratio_pressed()
elif data == "hcenter":
self._center_horizontal()
elif data == "vcenter":
self._center_vertical()
def update_editor_view(self, seek_tline_frame=False):
# This gets called when tline frame is changed from outside
# Call update_editor_view_with_frame that is used when udating from inside the object.
# seek_tline_frame will be False to stop endless loop of updates
frame = self.clip_editor.current_clip_frame
self.update_editor_view_with_frame(frame, seek_tline_frame)
def update_editor_view_with_frame(self, frame, seek_tline_frame=True):
self.update_slider_value_display(frame)
self.geom_kf_edit.set_clip_frame(frame)
self.buttons_row.set_frame(frame)
if seek_tline_frame == True:
self.seek_tline_frame(frame)
self.queue_draw()
def seek_tline_frame(self, clip_frame):
PLAYER().seek_frame(self.clip_tline_pos + clip_frame)
def update_property_value(self):
if self.initializing:
return
write_keyframes = []
for opa_kf, geom_kf in zip(self.clip_editor.keyframes, self.geom_kf_edit.keyframes):
frame, opacity = opa_kf
frame, rect, rubbish_opacity = geom_kf # rubbish_opacity was just doing same thing twice for nothing,
# and can be removed to clean up code, but could not bothered right now
write_keyframes.append((frame, rect, opacity))
self.editable_property.write_out_keyframes(write_keyframes)
def mouse_scroll_up(self):
view_size_index = self.geom_buttons_row.size_select.get_active()
view_size_index = view_size_index - 1
if view_size_index < 0:
view_size_index = 0
self.geom_buttons_row.size_select.set_active(view_size_index)
def mouse_scroll_down(self):
view_size_index = self.geom_buttons_row.size_select.get_active()
view_size_index = view_size_index + 1
if view_size_index > 2:
view_size_index = 2
self.geom_buttons_row.size_select.set_active(view_size_index)
class RotatingGeometryEditor(GeometryEditor):
def init_geom_gui(self, editable_property):
self.geom_kf_edit = keyframeeditcanvas.RotatingEditCanvas(editable_property, self)
self.geom_kf_edit.init_editor(current_sequence().profile.width(),
current_sequence().profile.height(),
GEOM_EDITOR_SIZE_MEDIUM)
self.geom_kf_edit.create_edit_points_and_values()
editable_property.value.strip('"')
self.geom_kf_edit.keyframe_parser = propertyparse.rotating_geom_keyframes_value_string_to_geom_kf_array
self.geom_kf_edit.set_keyframes(editable_property.value, editable_property.get_in_value)
def add_fade_in(self):
compositor = _get_current_edited_compositor()
keyframes = compositorfades.add_fade_in(compositor, 10) # updates editable_property.value. Remove fade length hardcoding in 2.4
if keyframes == None:
return # update failed, clip probably too short
self._update_all_for_kf_vec(keyframes)
def add_fade_out(self):
compositor = _get_current_edited_compositor()
keyframes = compositorfades.add_fade_out(compositor, 10) # updates editable_property.value. Remove fade length hardcoding in 2.4
if keyframes == None:
return # update failed, clip probably too short
self._update_all_for_kf_vec(keyframes)
def _update_all_for_kf_vec(self, keyframes):
self.editable_property.write_out_keyframes(keyframes)
self.editable_property.update_prop_value()
self.geom_kf_edit.set_keyframes(self.editable_property.value, self.editable_property.get_in_value)
self.clip_editor.keyframes = self.get_clip_editor_keyframes()
self.clip_editor.widget.queue_draw()
self.update_editor_view()
class FilterRectGeometryEditor(AbstractKeyFrameEditor):
def __init__(self, editable_property, use_clip_in=True):
AbstractKeyFrameEditor.__init__(self, editable_property, use_clip_in)
self.init_geom_gui(editable_property)
self.init_non_geom_gui()
def init_geom_gui(self, editable_property):
self.geom_kf_edit = keyframeeditcanvas.BoxEditCanvas(editable_property, self)
self.geom_kf_edit.init_editor(current_sequence().profile.width(),
current_sequence().profile.height(),
GEOM_EDITOR_SIZE_MEDIUM)
editable_property.value.strip('"')
self.geom_kf_edit.keyframe_parser = propertyparse.rect_keyframes_value_string_to_geom_kf_array
self.geom_kf_edit.set_keyframes(editable_property.value, editable_property.get_in_value)
def init_non_geom_gui(self):
# Create components
self.geom_buttons_row = GeometryEditorButtonsRow(self, True)
g_frame = Gtk.Frame()
g_frame.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
g_frame.add(self.geom_kf_edit.widget)
self.buttons_row = ClipEditorButtonsRow(self, True, False)
self.pos_entries_row = PositionNumericalEntries(self.geom_kf_edit, self, None)
# Create clip editor keyframes from geom editor keyframes
# that contain the property values when opening editor.
# From now on clip editor opacity values are used until editor is discarded.
self.clip_editor.keyframes = self.get_clip_editor_keyframes()
# Build gui
self.pack_start(g_frame, False, False, 0)
self.pack_start(self.geom_buttons_row, False, False, 0)
self.pack_start(self.pos_entries_row, False, False, 0)
self.pack_start(self.clip_editor.widget, False, False, 0)
self.pack_start(self.buttons_row, False, False, 0)
orig_tline_frame = PLAYER().current_frame()
self.clip_editor.add_keyframe(self.clip_editor.current_clip_frame)
self.geom_kf_edit.add_keyframe(self.clip_editor.current_clip_frame)
self.active_keyframe_changed() # to do update gui to current values
# This also seeks tline frame to frame 0, thus value was saved in the line above
# If we do not want to seek to kf 0 or clip start we, need seek back to original tline frame
self.display_tline_frame(orig_tline_frame)
PLAYER().seek_frame(orig_tline_frame)
self.queue_draw()
def active_keyframe_changed(self):
kf_frame = self.clip_editor.get_active_kf_frame()
self.update_editor_view_with_frame(kf_frame)
self.buttons_row.set_kf_info(self.clip_editor.get_kf_info())
# we need active index from clip_editor and geometry values from geom_kf_edit to update numerical entries
self.pos_entries_row.update_entry_values(self.geom_kf_edit.get_keyframe(self.clip_editor.active_kf_index))
def clip_editor_frame_changed(self, clip_frame):
self.seek_tline_frame(clip_frame)
self.buttons_row.set_frame(clip_frame)
def add_pressed(self):
self.clip_editor.add_keyframe(self.clip_editor.current_clip_frame)
self.geom_kf_edit.add_keyframe(self.clip_editor.current_clip_frame)
frame = self.clip_editor.get_active_kf_frame()
self.pos_entries_row.update_entry_values(self.geom_kf_edit.get_keyframe(self.clip_editor.active_kf_index))
self.update_editor_view_with_frame(frame)
self.update_property_value()
self.buttons_row.set_kf_info(self.clip_editor.get_kf_info())
def delete_pressed(self):
self.clip_editor.delete_active_keyframe()
self.update_editor_view()
self.update_property_value()
self.buttons_row.set_kf_info(self.clip_editor.get_kf_info())
def get_copy_kf_value(self):
return self.clip_editor.get_active_kf_value()
def paste_kf_value(self, value_data):
self.clip_editor.set_active_kf_value(value_data)
self.update_editor_view()
self.update_property_value()
def next_pressed(self):
self.clip_editor.set_next_active()
self.update_editor_view()
self.buttons_row.set_kf_info(self.clip_editor.get_kf_info())
def prev_pressed(self):
self.clip_editor.set_prev_active()
self.update_editor_view()
self.buttons_row.set_kf_info(self.clip_editor.get_kf_info())
def prev_frame_pressed(self):
self.clip_editor.move_clip_frame(-1)
self.update_editor_view()
self.buttons_row.set_kf_info(self.clip_editor.get_kf_info())
def next_frame_pressed(self):
self.clip_editor.move_clip_frame(1)
self.update_editor_view()
def move_kf_next_frame_pressed(self):
current_frame = self.clip_editor.get_active_kf_frame()
self.clip_editor.active_kf_pos_entered(current_frame + 1)
self.update_property_value()
self.update_editor_view()
def move_kf_prev_frame_pressed(self):
current_frame = self.clip_editor.get_active_kf_frame()
self.clip_editor.active_kf_pos_entered(current_frame - 1)
self.update_property_value()
self.update_editor_view()
def slider_value_changed(self, adjustment):
print (adjustment)
def get_clip_editor_keyframes(self):
keyframes = []
for kf in self.geom_kf_edit.keyframes:
frame, rect, opacity = kf
clip_kf = (frame, opacity)
keyframes.append(clip_kf)
return keyframes
def geometry_edit_started(self): # callback from geom_kf_edit
self.clip_editor.add_keyframe(self.clip_editor.current_clip_frame)
self.geom_kf_edit.add_keyframe(self.clip_editor.current_clip_frame)
def geometry_edit_finished(self): # callback from geom_kf_edit
self.geom_kf_edit.set_keyframe_to_edit_shape(self.clip_editor.active_kf_index)
self.update_editor_view_with_frame(self.clip_editor.current_clip_frame)
self.update_property_value()
self.buttons_row.set_kf_info(self.clip_editor.get_kf_info())
self.pos_entries_row.update_entry_values(self.geom_kf_edit.get_keyframe(self.clip_editor.active_kf_index))
def numerical_edit_done(self, new_shape):
# Callback from PositionNumericalEntries
self.geom_kf_edit.set_keyframe_to_edit_shape(self.clip_editor.active_kf_index, new_shape)
self.update_editor_view_with_frame(self.clip_editor.current_clip_frame)
self.update_property_value()
self.buttons_row.set_kf_info(self.clip_editor.get_kf_info())
self.pos_entries_row.update_entry_values(self.geom_kf_edit.get_keyframe(self.clip_editor.active_kf_index))
def update_request_from_geom_editor(self): # callback from geom_kf_edit
self.update_editor_view_with_frame(self.clip_editor.current_clip_frame)
self.pos_entries_row.update_entry_values(self.geom_kf_edit.get_keyframe(self.clip_editor.active_kf_index))
def keyframe_dragged(self, active_kf, frame):
self.geom_kf_edit.set_keyframe_frame(active_kf, frame)
def update_editor_view(self, seek_tline_frame=True):
# This gets called when tline frame is changed from outside
# Call update_editor_view_with_frame that is used when udating from inside the object.
# seek_tline_frame will be False to stop endless loop of updates
frame = self.clip_editor.current_clip_frame
self.update_editor_view_with_frame(frame, seek_tline_frame)
def update_editor_view_with_frame(self, frame, seek_tline_frame=True):
self.update_slider_value_display(frame)
self.geom_kf_edit.set_clip_frame(frame)
self.buttons_row.set_frame(frame)
if seek_tline_frame == True:
self.seek_tline_frame(frame)
self.queue_draw()
def update_property_value(self):
if self.initializing:
return
write_keyframes = []
for opa_kf, geom_kf in zip(self.clip_editor.keyframes, self.geom_kf_edit.keyframes):
frame, opacity = opa_kf
frame, rect, rubbish_opacity = geom_kf # rubbish_opacity was just doing same thing twice for nothing,
# and can be removed to clean up code, but could not bothered right now
write_keyframes.append((frame, rect, opacity))
self.editable_property.write_out_keyframes(write_keyframes)
class RotoMaskKeyFrameEditor(Gtk.VBox):
"""
Class combines named value slider with ClipKeyFrameEditor and
control buttons to create keyframe editor for a single keyframed
numerical value property.
"""
def __init__(self, editable_property, keyframe_parser):
GObject.GObject.__init__(self)
self.initializing = True # izneeded?!?
use_clip_in = True
self.set_homogeneous(False)
self.set_spacing(2)
self.editable_property = editable_property
self.clip_tline_pos = editable_property.get_clip_tline_pos()
self.clip_editor = ClipKeyFrameEditor(editable_property, self, use_clip_in)
self.clip_editor.mouse_listener = self
"""
Callbacks from ClipKeyFrameEditor:
def clip_editor_frame_changed(self, frame)
def active_keyframe_changed(self)
def keyframe_dragged(self, active_kf, frame)
def update_slider_value_display(self, frame)
Via clip_editor.mouse_listener
def mouse_pos_change_done(self)
"""
# Some filters start keyframes from *MEDIA* frame 0
# Some filters or compositors start keyframes from *CLIP* frame 0
# Filters starting from *media* 0 need offset to clip start added to all values
self.use_clip_in = use_clip_in
if self.use_clip_in == True:
self.clip_in = editable_property.clip.clip_in
else:
self.clip_in = 0
self.initializing = False # Hack against too early for on slider listner
self.clip_editor.keyframe_parser = keyframe_parser
editable_property.value.strip('"') # This has been an issue sometimes
self.clip_editor.set_keyframes(editable_property.value, editable_property.get_in_value)
clip_editor_row = Gtk.HBox(False, 0)
clip_editor_row.pack_start(self.clip_editor.widget, True, True, 0)
clip_editor_row.pack_start(guiutils.pad_label(4, 4), False, False, 0)
self.buttons_row = ClipEditorButtonsRow(self, True, False)
self.pack_start(clip_editor_row, False, False, 0)
self.pack_start(self.buttons_row, False, False, 0)
self.set_editor_sensitive(False)
def set_parent_editor(self, parent):
# parent implements callback:
# parent.update_view(timeline_frame)
self.parent = parent
def active_keyframe_changed(self):
frame = self.clip_editor.current_clip_frame
keyframes = self.clip_editor.keyframes
value = _get_frame_value(frame, keyframes)
self.buttons_row.set_frame(frame)
self.seek_tline_frame(frame)
self.buttons_row.set_kf_info(self.clip_editor.get_kf_info())
self.parent.update_view()
def update_slider_value_display(self, clip_frame):
# we don't have slider but this gets called from ClipKeyFrameEditor
pass
def mouse_pos_change_done(self):
# we make ClipKeyFrameEditor call this to update parent view
self.parent.update_view()
def clip_editor_frame_changed(self, clip_frame):
self.seek_tline_frame(clip_frame)
self.buttons_row.set_frame(clip_frame)
def add_pressed(self):
self.clip_editor.add_keyframe(self.clip_editor.current_clip_frame)
self.update_editor_view()
self.update_property_value()
self.buttons_row.set_kf_info(self.clip_editor.get_kf_info())
self.parent.update_view()
self.parent.update_effects_editor_value_labels()
def delete_pressed(self):
self.clip_editor.delete_active_keyframe()
self.update_editor_view()
self.update_property_value()
self.buttons_row.set_kf_info(self.clip_editor.get_kf_info())
self.parent.update_view()
self.parent.update_effects_editor_value_labels()
def next_pressed(self):
self.clip_editor.set_next_active()
self.update_editor_view()
self.buttons_row.set_kf_info(self.clip_editor.get_kf_info())
self.parent.update_view()
def prev_pressed(self):
self.clip_editor.set_prev_active()
self.update_editor_view()
self.buttons_row.set_kf_info(self.clip_editor.get_kf_info())
self.parent.update_view()
def prev_frame_pressed(self):
self.clip_editor.move_clip_frame(-1)
self.update_editor_view()
self.buttons_row.set_kf_info(self.clip_editor.get_kf_info())
self.parent.update_view()
def next_frame_pressed(self):
self.clip_editor.move_clip_frame(1)
self.update_editor_view()
self.parent.update_view()
def move_kf_next_frame_pressed(self):
current_frame = self.clip_editor.get_active_kf_frame()
self.clip_editor.active_kf_pos_entered(current_frame + 1)
self.update_property_value()
self.update_editor_view()
self.parent.update_view()
def move_kf_prev_frame_pressed(self):
current_frame = self.clip_editor.get_active_kf_frame()
self.clip_editor.active_kf_pos_entered(current_frame - 1)
self.update_property_value()
self.update_editor_view()
self.parent.update_view()
def keyframe_dragged(self, active_kf, frame):
pass
def update_editor_view(self, seek_tline=True):
frame = self.clip_editor.current_clip_frame
keyframes = self.clip_editor.keyframes
value = _get_frame_value(frame, keyframes)
self.buttons_row.set_frame(frame)
if seek_tline == True:
self.seek_tline_frame(frame)
self.queue_draw()
def update_property_value(self):
self.editable_property.write_out_keyframes(self.clip_editor.keyframes)
def display_tline_frame(self, tline_frame):
# This is called after timeline current frame changed.
# If timeline pos changed because drag is happening _here_,
# updating once more is wrong
if self.clip_editor.drag_on == True:
return
# update clipeditor pos
clip_frame = tline_frame - self.clip_tline_pos + self.clip_in
self.clip_editor.set_and_display_clip_frame(clip_frame)
self.update_editor_view(False)
def update_clip_pos(self):
# This is called after position of clip has been edited.
# We'll need to update some values to get keyframes on correct positions again
self.editable_property.update_clip_index()
self.clip_tline_pos = self.editable_property.get_clip_tline_pos()
if self.use_clip_in == True:
self.clip_in = self.editable_property.clip.clip_in
else:
self.clip_in = 0
self.clip_editor.clip_in = self.editable_property.clip.clip_in
self.clip_editor.widget.queue_draw()
def seek_tline_frame(self, clip_frame):
PLAYER().seek_frame(self.clip_tline_pos + clip_frame - self.clip_in)
def set_editor_sensitive(self, sensitive):
self.buttons_row.set_buttons_sensitive(sensitive)
self.clip_editor.set_sensitive(sensitive)
self.clip_editor.widget.queue_draw()
# ----------------------------------------------------------------- POSITION NUMERICAL ENTRY WIDGET
class PositionNumericalEntries(Gtk.HBox):
def __init__(self, geom_editor, parent_editor, editor_buttons):
GObject.GObject.__init__(self)
self.parent_editor = parent_editor
if isinstance(geom_editor, keyframeeditcanvas.RotatingEditCanvas):
self.rotating_geom = True
self.init_for_roto_geom(editor_buttons)
else:
self.rotating_geom = False
self.init_for_box_geom(editor_buttons)
def init_for_box_geom(self, editor_buttons):
x_label = Gtk.Label(_("X:"))
y_label = Gtk.Label(_("Y:"))
w_label = Gtk.Label(_("Width:"))
h_label = Gtk.Label(_("Height:"))
self.x_entry = Gtk.Entry.new()
self.y_entry = Gtk.Entry.new()
self.w_entry = Gtk.Entry.new()
self.h_entry = Gtk.Entry.new()
self.prepare_entry(self.x_entry)
self.prepare_entry(self.y_entry)
self.prepare_entry(self.w_entry)
self.prepare_entry(self.h_entry)
self.set_homogeneous(False)
self.set_spacing(2)
self.set_margin_top (4)
if editor_buttons != None: # We smetimes put editor buttons elsewhere
self.pack_start(editor_buttons, False, False, 0)
self.pack_start(Gtk.Label(), True, True, 0)
self.pack_start(x_label, False, False, 0)
self.pack_start(self.x_entry, False, False, 0)
self.pack_start(guiutils.pad_label(6, 6), False, False, 0)
self.pack_start(y_label, False, False, 0)
self.pack_start(self.y_entry, False, False, 0)
self.pack_start(guiutils.pad_label(6, 6), False, False, 0)
self.pack_start(w_label, False, False, 0)
self.pack_start(self.w_entry, False, False, 0)
self.pack_start(guiutils.pad_label(6, 6), False, False, 0)
self.pack_start(h_label, False, False, 0)
self.pack_start(self.h_entry, False, False, 0)
self.pack_start(Gtk.Label(), True, True, 0)
def init_for_roto_geom(self, editor_buttons):
# [960.0, 540.0, 1.0, 1.0, 0.0]
x_label = Gtk.Label(_("X:"))
y_label = Gtk.Label(_("Y:"))
x_scale_label = Gtk.Label(_("X scale:"))
y_scale_label = Gtk.Label(_("Y scale:"))
rotation_label = Gtk.Label(_("Rotation:"))
self.x_entry = Gtk.Entry.new()
self.y_entry = Gtk.Entry.new()
self.x_scale_entry = Gtk.Entry.new()
self.y_scale_entry = Gtk.Entry.new()
self.rotation_entry = Gtk.Entry.new()
self.prepare_entry(self.x_entry)
self.prepare_entry(self.y_entry)
self.prepare_entry(self.x_scale_entry)
self.prepare_entry(self.y_scale_entry)
self.prepare_entry(self.rotation_entry)
self.set_homogeneous(False)
self.set_spacing(2)
self.set_margin_top (4)
if editor_buttons != None: # We smetimes put editor buttons elsewhere
self.pack_start(editor_buttons, False, False, 0)
self.pack_start(Gtk.Label(), True, True, 0)
self.pack_start(x_label, False, False, 0)
self.pack_start(self.x_entry, False, False, 0)
self.pack_start(guiutils.pad_label(6, 6), False, False, 0)
self.pack_start(y_label, False, False, 0)
self.pack_start(self.y_entry, False, False, 0)
self.pack_start(guiutils.pad_label(6, 6), False, False, 0)
self.pack_start(x_scale_label, False, False, 0)
self.pack_start(self.x_scale_entry, False, False, 0)
self.pack_start(guiutils.pad_label(6, 6), False, False, 0)
self.pack_start(y_scale_label, False, False, 0)
self.pack_start(self.y_scale_entry, False, False, 0)
self.pack_start(guiutils.pad_label(6, 6), False, False, 0)
self.pack_start(rotation_label, False, False, 0)
self.pack_start(self.rotation_entry, False, False, 0)
self.pack_start(guiutils.pad_label(1, 6), False, False, 0)
if editor_buttons != None: # We smetimes put editor buttons elsewhere
self.pack_start(guiutils.pad_label(1, 6), False, False, 0)
else:
self.pack_start(Gtk.Label(), True, True, 0)
def prepare_entry(self, entry):
entry.set_width_chars (4)
entry.set_max_length (4)
entry.set_max_width_chars (4)
entry.connect("activate", self.enter_pressed)
def enter_pressed(self, entry):
if self.rotating_geom == True:
try:
x = float(self.x_entry.get_text())
y = float(self.y_entry.get_text())
xs = float(self.x_scale_entry.get_text())
ys = float(self.y_scale_entry.get_text())
rot = float(self.rotation_entry.get_text())
shape = [x, y, xs, ys, rot]
self.parent_editor.numerical_edit_done(shape)
except Exception as e:
# If user inputs non-ifloats we will just do nothing
print("Numerical input Exception - ", e)
else:
try:
x = float(self.x_entry.get_text())
y = float(self.y_entry.get_text())
w = float(self.w_entry.get_text())
h = float(self.h_entry.get_text())
shape = [x, y, w, h]
self.parent_editor.numerical_edit_done(shape)
except Exception as e:
# If user inputs non-ifloats we will just do nothing
print("Numerical input Exception - ", e)
def update_entry_values(self, active_kf):
frame, shape, opacity = active_kf
if self.rotating_geom == False:
x, y, w, h = shape
self.x_entry.set_text(str(x))
self.y_entry.set_text(str(y))
self.w_entry.set_text(str(w))
self.h_entry.set_text(str(h))
else:
x, y, xs, ys, rot = shape
self.x_entry.set_text(str(x))
self.y_entry.set_text(str(y))
self.x_scale_entry.set_text(str(xs))
self.y_scale_entry.set_text(str(ys))
self.rotation_entry.set_text(str(rot))
# ----------------------------------------------------------------- linear interpolation
def _get_frame_value(frame, keyframes):
for i in range(0, len(keyframes)):
kf_frame, kf_value = keyframes[i]
if kf_frame == frame:
return kf_value
try:
# See if frame between this and next keyframe
frame_n, value_n = keyframes[i + 1]
if ((kf_frame < frame)
and (frame < frame_n)):
time_fract = float((frame - kf_frame)) / float((frame_n - kf_frame))
value_range = value_n - kf_value
return kf_value + time_fract * value_range
except: # past last frame, use its value
return kf_value
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/kftoolmode.py 0000664 0000000 0000000 00000161540 13610327166 0025627 0 ustar 00root root 0000000 0000000 """
Flowblade Movie Editor is a nonlinear video editor.
Copyright 2012 Janne Liljeblad.
This file is part of Flowblade Movie Editor .
Flowblade Movie Editor 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.
Flowblade Movie Editor is distributed in the hope that 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 Flowblade Movie Editor. If not, see .
"""
"""
Module handles Keyframe tool functionality
"""
from gi.repository import Pango, PangoCairo, Gtk
import cairo
import math
import appconsts
import cairoarea
import clipeffectseditor
import dialogutils
import edit
from editorstate import current_sequence
from editorstate import PLAYER
import gui
import guiutils
import mltfilters
import propertyedit
import propertyparse
import respaths
import tlinewidgets
import updater
# Icons
HAMBURGER_ICON = None
ACTIVE_KF_ICON = None
NON_ACTIVE_KF_ICON = None
# Draw params
EDIT_AREA_HEIGHT = 200
END_PAD = 8
TOP_PAD = 23
HEIGHT_PAD_PIXELS_TOTAL = 44
OUT_OF_RANGE_ICON_PAD = 27
OUT_OF_RANGE_KF_ICON_HALF = 6
OUT_OF_RANGE_NUMBER_X_START = 7
OUT_OF_RANGE_NUMBER_X_END_PAD = 14
KF_ICON_Y_PAD = -6
KF_TEXT_PAD = -6
KF_LOWER_OFF = 11
# Kf edit params
KF_HIT_WIDTH = 8
KF_DRAG_THRESHOLD = 3
# Colors
FRAME_SCALE_LINES = (0.4, 0.4, 0.6) #(0.07, 0.07, 0.32)
FRAME_SCALE_LINES_BRIGHT = (0.2, 0.2, 0.6)
TEXT_COLOR = (0.6, 0.6, 0.6)
CURVE_COLOR = (0.97, 0.97, 0.30, 1)#(0.71, 0.13, 0.64, 1.0) # (0.19, 0.69, 0.15, 1) #
OVERLAY_BG = (0.0, 0.0, 0.0, 0.8)
VALUE_AREA_COLOR = (0.27, 0.27, 0.62, 0.85)
SOURCE_TRIANGLE_COLOR = (0.19, 0.32, 0.57)
SOURCE_TRIANGLE_OUTLINE_COLOR = (0.9, 0.9, 0.9)
SCALE_LINES_TEXT_COLOR = (0.9, 0.9, 0.9)
CLIP_OUTLINE_COLOR = (0.7, 0.7, 0.5, 0.22)
AUDIO_LEVELS_COLOR = (0.4, 0.4, 0.68, 0.20)
# Edit types
VOLUME_KF_EDIT = 0
BRIGHTNESS_KF_EDIT = 1
PARAM_KF_EDIT = 2
# Editor states
KF_DRAG = 0
POSITION_DRAG = 1
KF_DRAG_DISABLED = 2 # Not used currently
KF_DRAG_FRAME_ZERO_KF = 3
DRAG_MIN_Y = 4 # to make strt value slightly macnetic, makes easier to move position without changing value
hamburger_menu = Gtk.Menu()
oor_before_menu = Gtk.Menu()
oor_after_menu = Gtk.Menu()
value_snapping_menu = Gtk.Menu()
params_menu = Gtk.Menu()
edit_data = None
enter_mode = None
_kf_editor = None
_playhead_follow_kf = True
_snapping = 1
# -------------------------------------------------- init
def load_icons():
global HAMBURGER_ICON, ACTIVE_KF_ICON, NON_ACTIVE_KF_ICON
# Aug-2019 - SvdB - BB
HAMBURGER_ICON = guiutils.get_cairo_image("hamburger")
ACTIVE_KF_ICON = guiutils.get_cairo_image("kf_active")
NON_ACTIVE_KF_ICON = guiutils.get_cairo_image("kf_not_active_tool")
def init_tool_for_clip(clip, track, edit_type=VOLUME_KF_EDIT, param_data=None):
# These can produce data for same objects we choose not to commit to updating
# clipeffectseditor/kftool with events from each other.
clipeffectseditor.clear_clip()
clip_index = track.clips.index(clip)
# Save data needed to do the keyframe edits.
global edit_data #, pressed_on_selected, drag_disabled
edit_data = {"draw_function":_tline_overlay,
"clip_index":clip_index,
"clip_start_in_timeline":track.clip_start(clip_index),
"clip":clip,
"track":track,
"initializing":True}
if edit_type == PARAM_KF_EDIT:
pass # We are not trying to decide based on track what to edit
else:
# Always brightness keyframes for media types that contain no audio.
if edit_data["clip"].media_type != appconsts.VIDEO and edit_data["clip"].media_type != appconsts.AUDIO:
edit_type = BRIGHTNESS_KF_EDIT
# Volume keyframes on audio track for video and audio
if track.type == appconsts.AUDIO and not(edit_data["clip"].media_type != appconsts.VIDEO and edit_data["clip"].media_type != appconsts.AUDIO):
edit_type = VOLUME_KF_EDIT
global _kf_editor
# Init for edit type
if edit_type == VOLUME_KF_EDIT:
ep = _get_volume_editable_property(clip, track, clip_index)
if ep == None:
filter_info = mltfilters.get_volume_filters_info()
data = {"clip":clip,
"filter_info":filter_info,
"filter_edit_done_func":_filter_create_dummy_func}
action = edit.add_multipart_filter_action(data)
action.do_edit()
ep = _get_volume_editable_property(clip, track, clip_index)
edit_data["editable_property"] = ep
_kf_editor = TLineKeyFrameEditor(ep, True, VOLUME_KF_EDIT)
elif edit_type == BRIGHTNESS_KF_EDIT:
ep = _get_brightness_editable_property(clip, track, clip_index)
if ep == None:
filter_info = mltfilters.get_brightness_filter_info()
data = {"clip":clip,
"filter_info":filter_info,
"filter_edit_done_func":_filter_create_dummy_func}
action = edit.add_filter_action(data)
action.do_edit()
ep = _get_brightness_editable_property(clip, track, clip_index)
edit_data["editable_property"] = ep
_kf_editor = TLineKeyFrameEditor(ep, True, BRIGHTNESS_KF_EDIT)
else: # edit_type == PARAM_KF_EDIT
property_name, filter_object, filter_index, disp_name = param_data
ep = _get_param_editable_property(property_name, clip, track, clip_index, filter_object, filter_index)
# create kf in frame 0 if value PROP_INT or PROP_FLOAT and kf expression
eq_index = ep.value.find("=")
if eq_index == -1:
new_value = "0=" + ep.value
ep.value = new_value
ep.write_filter_object_property(new_value)
# Turn into keyframe property
ep = ep.get_as_KeyFrameHCSFilterProperty()
edit_data["editable_property"] = ep
filter_param_name = filter_object.info.name + ":" + disp_name
_kf_editor = TLineKeyFrameEditor(ep, True, PARAM_KF_EDIT, filter_param_name)
tlinewidgets.set_edit_mode_data(edit_data)
updater.repaint_tline()
def update_clip_frame(tline_frame):
if _kf_editor != None and edit_data != None and edit_data["initializing"] != True:
clip_frame = tline_frame - edit_data["clip_start_in_timeline"] + edit_data["clip"].clip_in
_kf_editor.set_and_display_clip_frame(clip_frame)
def _get_volume_editable_property(clip, track, clip_index):
return _get_multipart_keyframe_ep_from_service(clip, track, clip_index, "volume")
def _get_brightness_editable_property(clip, track, clip_index):
for i in range(0, len(clip.filters)):
filter_object = clip.filters[i]
if filter_object.info.mlt_service_id == "brightness":
editable_properties = propertyedit.get_filter_editable_properties(
clip,
filter_object,
i,
track,
clip_index)
for ep in editable_properties:
try:
if ep.name == "level":
return ep
except:
pass
return None
def _get_param_editable_property(property_name, clip, track, clip_index, filter_object, filter_index):
editable_properties = propertyedit.get_filter_editable_properties(
clip,
filter_object,
filter_index,
track,
clip_index)
for ep in editable_properties:
try:
if ep.name == property_name:
return ep
except:
pass
return None
def _get_multipart_keyframe_ep_from_service(clip, track, clip_index, mlt_service_id):
for i in range(0, len(clip.filters)):
filter_object = clip.filters[i]
if filter_object.info.mlt_service_id == mlt_service_id:
editable_properties = propertyedit.get_filter_editable_properties(
clip,
filter_object,
i,
track,
clip_index)
for ep in editable_properties:
try:
if ep.args["exptype"] == "multipart_keyframe":
return ep
except:
pass
return None
def exit_tool():
set_no_clip_edit_data()
global enter_mode
if enter_mode != None:
gui.editor_window.kf_tool_exit_to_mode(enter_mode)
enter_mode = None
updater.repaint_tline()
def _filter_create_dummy_func(obj1, obj2):
pass
# ---------------------------------------------- mouse events
def mouse_press(event, frame):
x = event.x
y = event.y
# If we have clip being edited and its edit area is hit, we do not need to init data.
# If editor open we disregard track locking until it is closed.
if _kf_editor != None and _kf_editor.overlay_area_hit(x, y):
_handle_edit_mouse_press(event)
return
# Get pressed track
track = tlinewidgets.get_track(y)
# Selecting empty clears selection
if track == None or track.id == 0 or track.id == len(current_sequence().tracks) - 1:
exit_tool()
return
# No edits for locked tracks
if dialogutils.track_lock_check_and_user_info(track):
set_no_clip_edit_data()
return
# Attempt to init kf tool editing on some clip
# Get pressed clip index
clip_index = current_sequence().get_clip_index(track, frame)
# Selecting empty clears selection
if clip_index == -1:
exit_tool()
return
clip = track.clips[clip_index]
init_tool_for_clip(clip, track)
def _handle_edit_mouse_press(event):
_kf_editor.press_event(event)
def mouse_move(x, y, frame, state):
if _kf_editor != None and edit_data != None and edit_data["initializing"] != True:
_kf_editor.motion_notify_event(x, y, state)
def mouse_release(x, y, frame, state):
if _kf_editor != None and edit_data != None and edit_data["initializing"] != True:
_kf_editor.release_event(x, y)
if edit_data != None:
edit_data["initializing"] = False
# -------------------------------------------- edit
def delete_active_keyframe():
if _kf_editor != None and edit_data != None and edit_data["initializing"] != True:
_kf_editor.delete_active_keyframe()
def _clip_is_being_edited():
if edit_data == None:
return False
if edit_data["clip_index"] == -1:
return False
return True
def set_no_clip_edit_data():
# set edit data to reflect that no clip is being edited currently.
global edit_data, _kf_editor
edit_data = {"draw_function":_tline_overlay,
"clip_index":-1,
"track":None,
"mouse_start_x":-1,
"mouse_start_y":-1}
_kf_editor = None
tlinewidgets.set_edit_mode_data(edit_data)
# ----------------------------------------------------------------------- draw callback from tlinewidgets.py
def _tline_overlay(cr):
if _clip_is_being_edited() == False:
return
track = edit_data["track"]
cx_start = tlinewidgets._get_frame_x(edit_data["clip_start_in_timeline"])
clip = track.clips[edit_data["clip_index"]]
cx_end = tlinewidgets._get_frame_x(track.clip_start(edit_data["clip_index"]) + clip.clip_out - clip.clip_in + 1) # +1 because out inclusive
# Get y position for clip's track
ty_bottom = tlinewidgets._get_track_y(1) + current_sequence().tracks[1].height
ty_top = tlinewidgets._get_track_y(len(current_sequence().tracks) - 2) - 6 # -6 is hand correction, no idea why the math isn't getting correct pos top most track
ty_top_bottom_edge = ty_top + EDIT_AREA_HEIGHT
off_step = float(ty_bottom - ty_top_bottom_edge) / float(len(current_sequence().tracks) - 2)
ty_off = off_step * float(track.id - 1)
ty = ty_bottom - ty_off
cy_start = ty - EDIT_AREA_HEIGHT
# Set draw params and draw
_kf_editor.set_allocation(cx_start, cy_start, cx_end - cx_start, EDIT_AREA_HEIGHT)
_kf_editor.source_track_center = tlinewidgets._get_track_y(track.id) + current_sequence().tracks[track.id].height / 2.0
_kf_editor.draw(cr)
# ----------------------------------------------------- editor object
class TLineKeyFrameEditor:
def __init__(self, editable_property, use_clip_in, edit_type, filter_param_name=None):
self.clip_length = editable_property.get_clip_length() - 1
self.edit_type = edit_type
# Some filters start keyframes from *MEDIA* frame 0
# Some filters or compositors start keyframes from *CLIP* frame 0
# Filters starting from *MEDIA* 0 need offset
# to clip start added to all values.
self.use_clip_in = use_clip_in
if self.use_clip_in == True:
self.clip_in = editable_property.clip.clip_in
else:
self.clip_in = 0
self.current_clip_frame = self.clip_in
self.clip_tline_pos = editable_property.get_clip_tline_pos()
self.keyframes = [(0, 0.0)]
self.active_kf_index = 0
self.frame_scale = tlinewidgets.KFToolFrameScale(FRAME_SCALE_LINES)
self.source_track_center = 0 # set externally
self.edit_value = None
self.mouse_x = -1
self.mouse_y = -1
self.media_frame_txt = _("Media Frame: ")
self.volume_kfs_text = _("Volume Keyframes")
self.brightness_kfs_text = _("Brightness Keyframes")
self.filter_param_name_txt = filter_param_name
self.current_mouse_action = None
self.drag_min = -1
self.drag_max = -1
# Init keyframes
self.keyframe_parser = propertyparse.single_value_keyframes_string_to_kf_array
editable_property.value.strip('"')
self.set_keyframes(editable_property.value, editable_property.get_in_value)
self._set_pos_to_active_kf()
# ---------------------------------------------------- data in
def set_keyframes(self, keyframes_str, out_to_in_func):
self.keyframes = self.keyframe_parser(keyframes_str, out_to_in_func)
def set_allocation(self, x, y, w, h):
self.allocation = (x, y, w, h)
# ------------------------------------------------------ tline seek
def clip_editor_frame_changed(self, clip_frame):
self.seek_tline_frame(clip_frame)
def seek_tline_frame(self, clip_frame):
PLAYER().seek_frame(self.clip_tline_pos + clip_frame - self.clip_in)
# ------------------------------------------------------ value write out
def update_property_value(self):
edit_data["editable_property"].write_out_keyframes(self.keyframes)
# ------------------------------------------------------- debug
def print_keyframes(self):
print("clip edit keyframes:")
for i in range(0, len(self.keyframes)):
print(self.keyframes[i])
# ----------------------------------------------------------------- Draw
def draw(self, cr):
"""
Callback for repaint from CairoDrawableArea.
We get cairo context and allocation.
"""
x, y, w, h = self.allocation
# Draw bg
cr.set_source_rgba(*OVERLAY_BG)
cr.rectangle(x, y, w, h)
cr.fill()
self._draw_edit_area_borders(cr)
# Top row
cr.set_source_surface(HAMBURGER_ICON, x + 4.5, y + 4)
cr.paint()
# Draw clip out line
clip = edit_data["clip"]
track = edit_data["track"]
pix_per_frame = tlinewidgets.pix_per_frame
pos = tlinewidgets.pos
clip_start_in_tline = edit_data["clip_start_in_timeline"]
clip_start_frame = clip_start_in_tline - pos
clip_in = clip.clip_in
clip_out = clip.clip_out
clip_length = clip_out - clip_in + 1 # +1 because in and out both inclusive
scale_length = clip_length * pix_per_frame
scale_in = clip_start_frame * pix_per_frame
clip_outline_y = tlinewidgets._get_track_y(track.id)
self.create_round_rect_path(cr, scale_in,
clip_outline_y, scale_length,
track.height)
cr.set_source_rgba(*CLIP_OUTLINE_COLOR)
cr.set_line_width(2.0)
cr.fill()
# Frame scale and value lines
self.frame_scale.draw(cr, edit_data["clip_start_in_timeline"], self.clip_length, self._get_upper_y(), self._get_lower_y())
self._draw_value_lines(cr, x, w)
kf_positions = self.get_clip_kfs_and_positions()
# Draw value curves, area fill and audio levels,they need to be clipped into edit area
cr.save()
ex, ey, ew, eh = self._get_edit_area_rect()
# Maybe draw audio levels
if self.edit_type == VOLUME_KF_EDIT and clip.is_blanck_clip == False and clip.waveform_data != None:
cr.set_source_rgba(*AUDIO_LEVELS_COLOR)
y_pad = TOP_PAD
bar_height = eh
# Draw all frames only if pixels per frame > 2, otherwise
# draw only every other or fewer frames
draw_pix_per_frame = tlinewidgets.pix_per_frame
if draw_pix_per_frame < 2:
draw_pix_per_frame = 2
step = int(2 / pix_per_frame)
if step < 1:
step = 1
else:
step = 1
# Draw only frames in display
draw_first = clip_in
draw_last = clip_out + 1
if clip_start_frame < 0:
draw_first = int(draw_first - clip_start_frame)
# Get media frame 0 position in screen pixels
media_start_pos_pix = scale_in - clip_in * pix_per_frame
mid_y = y + y_pad + eh / 2.0
# Draw level bar for each frame in draw range
for f in range(draw_first, draw_last, step):
try:
xf = media_start_pos_pix + f * pix_per_frame
hf = bar_height * clip.waveform_data[f] * 0.5
if h < 1:
h = 1
cr.rectangle(xf, mid_y - hf, draw_pix_per_frame, hf * 2.0)
except:
# This is just dirty fix a when 23.98 fps does not work
break
cr.fill()
# Draw value curve and area fill
cr.set_source_rgba(*CURVE_COLOR)
cr.set_line_width(3.0)
cr.rectangle(ex, ey, ew, eh)
cr.clip()
for i in range(0, len(kf_positions)):
kf, frame, kf_index, kf_pos_x, kf_pos_y = kf_positions[i]
# this trying to get rid of some draw artifacts by limiting x positions
if kf_pos_x < -10000:
kf_pos_x = -10000
if kf_pos_x > 10000:
kf_pos_x = 10000
if i == 0:
cr.move_to(kf_pos_x, kf_pos_y)
else:
cr.line_to(kf_pos_x, kf_pos_y)
# If last kf before clip end, continue value curve to end
kf, frame, kf_index, kf_pos_x, kf_pos_y = kf_positions[-1]
if kf_pos_x < ex + ew:
cr.move_to(kf_pos_x, kf_pos_y)
cr.line_to(ex + ew, kf_pos_y)
else:
cr.line_to(ex + ew, kf_pos_y)
cr.stroke()
cr.restore()
# Draw keyframes
for i in range(0, len(kf_positions)):
kf, frame, kf_index, kf_pos_x, kf_pos_y = kf_positions[i]
if frame < self.clip_in:
continue
if frame > self.clip_in + self.clip_length:
continue
if kf_index == self.active_kf_index:
icon = ACTIVE_KF_ICON
else:
icon = NON_ACTIVE_KF_ICON
cr.set_source_surface(icon, kf_pos_x - 6, kf_pos_y - 6) # -6 to get kf bitmap center on calculated pixel
cr.paint()
# Draw out-of-range kf icons and kf counts
if w > 55: # dont draw on too small editors
before_kfs = len(self.get_out_of_range_before_kfs())
after_kfs = len(self.get_out_of_range_after_kfs())
kfy = self._get_lower_y() + KF_LOWER_OFF
if before_kfs > 0:
cr.set_source_surface(NON_ACTIVE_KF_ICON, x + OUT_OF_RANGE_ICON_PAD - OUT_OF_RANGE_KF_ICON_HALF * 2, kfy + KF_ICON_Y_PAD)
cr.paint()
self._draw_text(cr, str(before_kfs), x + OUT_OF_RANGE_NUMBER_X_START, kfy + KF_TEXT_PAD)
if after_kfs > 0:
cr.set_source_surface(NON_ACTIVE_KF_ICON, x + w - OUT_OF_RANGE_ICON_PAD, kfy + KF_ICON_Y_PAD)
cr.paint()
self._draw_text(cr, str(after_kfs), x + w - OUT_OF_RANGE_NUMBER_X_END_PAD, kfy + KF_TEXT_PAD)
# Draw source triangle
cr.set_line_width(2.0)
cr.move_to(x - 8, self.source_track_center - 8)
cr.line_to(x + 1, self.source_track_center)
cr.line_to(x - 8, self.source_track_center + 8)
cr.close_path()
cr.set_source_rgb(*SOURCE_TRIANGLE_COLOR)
cr.fill_preserve()
cr.set_source_rgb(*SOURCE_TRIANGLE_OUTLINE_COLOR)
cr.stroke()
cr.move_to(x + w + 8, self.source_track_center - 8)
cr.line_to(x + w - 1, self.source_track_center)
cr.line_to(x + w + 8, self.source_track_center + 8)
cr.close_path()
cr.set_source_rgb(*SOURCE_TRIANGLE_COLOR)
cr.set_source_rgb(*SOURCE_TRIANGLE_COLOR)
cr.fill_preserve()
cr.set_source_rgb(*SOURCE_TRIANGLE_OUTLINE_COLOR)
cr.stroke()
# Draw frame pointer
try:
panel_pos = self._get_panel_pos()
except ZeroDivisionError: # math fails for 1 frame clip
panel_pos = END_PAD
cr.set_line_width(1.0)
cr.set_source_rgb(*FRAME_SCALE_LINES_BRIGHT)
cr.move_to(panel_pos, ey - 8)
cr.line_to(panel_pos, ey + eh + 8)
cr.stroke()
# Draw title
if w > 55: # dont draw on too small editors
if self.edit_type == VOLUME_KF_EDIT:
text = edit_data["editable_property"].clip.name + " - " + self.volume_kfs_text
elif self.edit_type == BRIGHTNESS_KF_EDIT:
text = edit_data["editable_property"].clip.name + " - " + self.brightness_kfs_text
else: # PARAM_KF_EDIT
text = edit_data["editable_property"].clip.name + " - " + self.filter_param_name_txt
self._draw_text(cr, text, -1, y + 4, True, x, w)
self._draw_text(cr, self.media_frame_txt + str(self.current_clip_frame), -1, kfy - 8, True, x, w)
# Value texts
self._draw_value_texts(cr, x, w)
# Value value info
if self.edit_value != None:
self._draw_value_text_box(cr, self.mouse_x,self.mouse_y, str(self.edit_value))
def _draw_edit_area_borders(self, cr):
x, y, w, h = self._get_edit_area_rect()
cr.set_source_rgb(*FRAME_SCALE_LINES)
cr.rectangle(x, y, w, h)
cr.stroke()
def _draw_value_lines(self, cr, x, w):
# Audio hard coded value lines
active_width = w - 2 * END_PAD
xs = x + END_PAD
xe = xs + active_width
if self.edit_type == VOLUME_KF_EDIT:
# 0
y = self._get_panel_y_for_value(0.0)
cr.set_line_width(1.0)
cr.set_source_rgb(*FRAME_SCALE_LINES)
cr.move_to(xs, y)
cr.line_to(xe, y)
cr.stroke()
# 50
y = self._get_panel_y_for_value(50)
cr.set_source_rgb(*FRAME_SCALE_LINES)
cr.move_to(xs, y)
cr.line_to(xe, y)
cr.stroke()
# 100
y = self._get_panel_y_for_value(100)
cr.set_source_rgb(*FRAME_SCALE_LINES)
cr.move_to(xs, y)
cr.line_to(xe, y)
cr.stroke()
elif self.edit_type == BRIGHTNESS_KF_EDIT:
# 0
y = self._get_panel_y_for_value(0.0)
cr.set_line_width(1.0)
cr.set_source_rgb(*FRAME_SCALE_LINES)
cr.move_to(xs, y)
cr.line_to(xe, y)
cr.stroke()
# 50
y = self._get_panel_y_for_value(50)
cr.set_line_width(1.0)
cr.set_source_rgb(*FRAME_SCALE_LINES)
cr.move_to(xs, y)
cr.line_to(xe, y)
cr.stroke()
# 100
y = self._get_panel_y_for_value(100)
cr.set_source_rgb(*FRAME_SCALE_LINES)
cr.move_to(xs, y)
cr.line_to(xe, y)
cr.stroke()
else:
editable_property = edit_data["editable_property"]
adjustment = editable_property.get_input_range_adjustment()
lower = adjustment.get_lower()
upper = adjustment.get_upper()
half = (upper - lower) / 2 + lower
# Min
y = self._get_panel_y_for_value(lower)
cr.set_line_width(1.0)
cr.set_source_rgb(*FRAME_SCALE_LINES)
cr.move_to(xs, y)
cr.line_to(xe, y)
cr.stroke()
# Half
y = self._get_panel_y_for_value(half)
cr.set_source_rgb(*FRAME_SCALE_LINES)
cr.move_to(xs, y)
cr.line_to(xe, y)
cr.stroke()
# Max
y = self._get_panel_y_for_value(upper)
cr.set_source_rgb(*FRAME_SCALE_LINES)
cr.move_to(xs, y)
cr.line_to(xe, y)
cr.stroke()
def _draw_value_texts(self, cr, x, w):
# Audio hard coded value lines
TEXT_X_OFF = 4
TEXT_X_OFF_END = -28
TEXT_Y_OFF = 4
active_width = w - 2 * END_PAD
xs = x + END_PAD
xe = xs + active_width
cr.select_font_face ("sans-serif",
cairo.FONT_SLANT_NORMAL,
cairo.FONT_WEIGHT_NORMAL)
cr.set_font_size(12)
cr.set_source_rgb(*SCALE_LINES_TEXT_COLOR)
if self.edit_type == VOLUME_KF_EDIT:
# 0
y = self._get_panel_y_for_value(0.0)
text = "0"
cr.move_to(xs + TEXT_X_OFF, y - TEXT_Y_OFF)
cr.show_text(text)
cr.move_to(xe + TEXT_X_OFF_END + 16, y - TEXT_Y_OFF)
cr.show_text(text)
# 50
y = self._get_panel_y_for_value(50)
text = "50"
cr.move_to(xs + TEXT_X_OFF, y - TEXT_Y_OFF + 8)
cr.show_text(text)
cr.move_to(xe + TEXT_X_OFF_END + 6, y - TEXT_Y_OFF + 8)
cr.show_text(text)
# 100
y = self._get_panel_y_for_value(100)
text = "100"
cr.move_to(xs + TEXT_X_OFF, y - TEXT_Y_OFF + 17)
cr.show_text(text)
cr.move_to(xe + TEXT_X_OFF_END, y - TEXT_Y_OFF + 17)
cr.show_text(text)
elif self.edit_type == BRIGHTNESS_KF_EDIT:
# 0
y = self._get_panel_y_for_value(0.0)
text = "0"
cr.move_to(xs + TEXT_X_OFF, y - TEXT_Y_OFF)
cr.show_text(text)
cr.move_to(xe + TEXT_X_OFF_END + 16, y - TEXT_Y_OFF)
cr.show_text(text)
# 50
y = self._get_panel_y_for_value(50)
text = "50"
cr.move_to(xs + TEXT_X_OFF, y + 4)
cr.show_text(text)
cr.move_to(xe + TEXT_X_OFF_END + 6, y + 4)
cr.show_text(text)
# 100
y = self._get_panel_y_for_value(100)
text = "100"
cr.move_to(xs + TEXT_X_OFF, y + 13)
cr.show_text(text)
cr.move_to(xe + TEXT_X_OFF_END, y + 13)
cr.show_text(text)
else:
editable_property = edit_data["editable_property"]
adjustment = editable_property.get_input_range_adjustment()
lower = adjustment.get_lower()
upper = adjustment.get_upper()
half = (upper - lower) / 2 + lower
# Min
y = self._get_panel_y_for_value(lower)
text = str(lower)
cr.move_to(xs + TEXT_X_OFF, y - TEXT_Y_OFF)
cr.show_text(text)
cr.move_to(xe + TEXT_X_OFF_END + 16, y - TEXT_Y_OFF)
cr.show_text(text)
# Half
y = self._get_panel_y_for_value(half)
text = str(half)
cr.move_to(xs + TEXT_X_OFF, y - TEXT_Y_OFF + 8)
cr.show_text(text)
cr.move_to(xe + TEXT_X_OFF_END + 6, y - TEXT_Y_OFF + 8)
cr.show_text(text)
# Max
y = self._get_panel_y_for_value(upper)
text = str(upper)
cr.move_to(xs + TEXT_X_OFF, y - TEXT_Y_OFF + 17)
cr.show_text(text)
cr.move_to(xe + TEXT_X_OFF_END, y - TEXT_Y_OFF + 17)
cr.show_text(text)
def _draw_text(self, cr, txt, x, y, centered=False, tline_x=-1, w=-1):
layout = PangoCairo.create_layout(cr)
layout.set_text(txt, -1)
desc = Pango.FontDescription("Sans 8")
layout.set_font_description(desc)
lw, lh = layout.get_pixel_size()
if centered == True:
x = w/2 - lw/2 + tline_x
if lw > w:
return
cr.move_to(x, y)
cr.set_source_rgb(*TEXT_COLOR)
PangoCairo.update_layout(cr, layout)
PangoCairo.show_layout(cr, layout)
def _draw_value_text_box(self, cr, x, y, text):
x = int(x)
y = int(y)
cr.set_source_rgb(1, 1, 1)
cr.select_font_face ("sans-serif",
cairo.FONT_SLANT_NORMAL,
cairo.FONT_WEIGHT_NORMAL)
cr.set_font_size(13)
x_bearing, y_bearing, width, height, x_advance, y_advance = cr.text_extents(text)
x1 = x - 3.5
y1 = y + 4.5
x2 = x + width + 5.5
y2 = y - height - 4.5
cr.move_to(x1, y1)
cr.line_to(x1, y2)
cr.line_to(x2, y2)
cr.line_to(x2, y1)
cr.close_path()
cr.set_source_rgb(0.1, 0.1, 0.1)
cr.fill_preserve()
cr.set_line_width(1.0)
cr.set_source_rgb(0.7, 0.7, 0.7)
cr.stroke()
cr.move_to(x, y)
cr.set_source_rgb(0.8, 0.8, 0.8)
cr.show_text(text)
def create_round_rect_path(self, cr, x, y, width, height, radius=4.0):
degrees = math.pi / 180.0
cr.new_sub_path()
cr.arc(x + width - radius, y + radius, radius, -90 * degrees, 0 * degrees)
cr.arc(x + width - radius, y + height - radius, radius, 0 * degrees, 90 * degrees)
cr.arc(x + radius, y + height - radius, radius, 90 * degrees, 180 * degrees)
cr.arc(x + radius, y + radius, radius, 180 * degrees, 270 * degrees)
cr.close_path()
# ----------------------------------------------------------- mouse events
def press_event(self, event):
"""
Mouse button callback
"""
# Check if menu icons hit
if self._oor_start_kf_hit(event.x, event.y) == True:
self._show_oor_before_menu(gui.tline_canvas.widget, event)
return
if self._oor_end_kf_hit(event.x, event.y) == True:
self._show_oor_after_menu(gui.tline_canvas.widget, event)
return
if self._hamburger_hit(event.x, event.y) == True:
self._show_hamburger_menu(gui.tline_canvas.widget, event)
return
lx = self._legalize_x(event.x)
ly = self._legalize_y(event.y)
self.value_drag_on = False
self.mouse_start_y = ly
self.mouse_x = lx
self.mouse_y = ly
if event.button == 3: # right mouse
self.current_mouse_action = POSITION_DRAG
self.drag_min = self.clip_in
self.drag_max = self.clip_in + self.clip_length
frame = self._get_drag_frame(lx)
self.current_clip_frame = frame
self.clip_editor_frame_changed(self.current_clip_frame)
updater.repaint_tline()
return
hit_kf = self._key_frame_hit(lx, ly)
if hit_kf == None: # nothing was hit, add new keyframe and set it active
frame = self._get_frame_for_panel_pos(lx)
value = round(self._get_value_for_panel_y(ly))
self.add_keyframe(frame, value)
hit_kf = self.active_kf_index
else: # some keyframe was pressed
self.active_kf_index = hit_kf
frame, value = self.keyframes[hit_kf]
self.edit_value = round(value)
self.current_clip_frame = frame
if hit_kf == 0:
self.current_mouse_action = KF_DRAG_FRAME_ZERO_KF
else:
self.current_mouse_action = KF_DRAG
prev_frame, val = self.keyframes[hit_kf - 1]
self.drag_min = prev_frame + 1
try:
next_frame, val = self.keyframes[hit_kf + 1]
self.drag_max = next_frame - 1
except:
self.drag_max = self.clip_in + self.clip_length
updater.repaint_tline()
def motion_notify_event(self, x, y, state):
"""
Mouse move callback
"""
lx = self._legalize_x(x)
ly = self._legalize_y(y)
if abs(self.mouse_start_y - ly) > DRAG_MIN_Y:
self.value_drag_on = True
self.mouse_x = lx
self.mouse_y = ly
if self.current_mouse_action == POSITION_DRAG:
frame = self._get_drag_frame(lx)
self.current_clip_frame = frame
self.clip_editor_frame_changed(self.current_clip_frame)
updater.repaint_tline()
elif self.current_mouse_action == KF_DRAG or self.current_mouse_action == KF_DRAG_FRAME_ZERO_KF:
frame = self._get_drag_frame(lx)
if self.current_mouse_action == KF_DRAG_FRAME_ZERO_KF:
frame = 0
if self.value_drag_on == True:
value = round(self._get_value_for_panel_y(ly))
if _snapping == 2:
value = round(value / 2) * 2
elif _snapping == 5:
value = round(value / 5.0) * 5
self.edit_value = value
self.set_active_kf_frame_and_value(frame, value)
else:
self.set_active_kf_frame_and_value(frame, self.edit_value)
if _playhead_follow_kf == True:
self.current_clip_frame = frame
self.clip_editor_frame_changed(self.current_clip_frame)
updater.repaint_tline()
def release_event(self, x,y):
"""
Mouse release callback.
"""
lx = self._legalize_x(x)
ly = self._legalize_y(y)
if abs(self.mouse_start_y - ly) < DRAG_MIN_Y:
value_drag_on = True
self.mouse_x = lx
self.mouse_y = ly
if self.current_mouse_action == POSITION_DRAG:
frame = self._get_drag_frame(lx)
self.current_clip_frame = frame
self.clip_editor_frame_changed(self.current_clip_frame)
updater.repaint_tline()
elif self.current_mouse_action == KF_DRAG or self.current_mouse_action == KF_DRAG_FRAME_ZERO_KF:
frame = self._get_drag_frame(lx)
if self.current_mouse_action == KF_DRAG_FRAME_ZERO_KF:
frame = 0
if self.value_drag_on == True:
value = round(self._get_value_for_panel_y(ly))
if _snapping == 2:
value = round(value / 2) * 2
elif _snapping == 5:
value = round(value / 5) * 5
self.set_active_kf_frame_and_value(frame, value)
self.hack_fix_for_zero_one_keyframe_problem()
else:
self.set_active_kf_frame_and_value(frame, self.edit_value)
self.hack_fix_for_zero_one_keyframe_problem()
if _playhead_follow_kf == True:
self.current_clip_frame = frame
self.clip_editor_frame_changed(self.current_clip_frame)
self.update_property_value()
self.edit_value = None
updater.repaint_tline()
self.current_mouse_action = None
# --------------------------------------------------------------- keyframes funcs
def get_clip_kfs_and_positions(self):
kf_positions = []
for i in range(0, len(self.keyframes)):
frame, value = self.keyframes[i]
try:
kf_pos_x = self._get_panel_pos_for_frame(frame)
except ZeroDivisionError: # math fails for 1 frame clip
kf_pos_x = END_PAD
kf_pos_y = self._get_panel_y_for_value(value)
kf_positions.append((self.keyframes[i], frame, i, kf_pos_x, kf_pos_y))
return kf_positions
def get_out_of_range_before_kfs(self):
# returns Keyframes before current clip start
kfs = []
for i in range(0, len(self.keyframes)):
frame, value = self.keyframes[i]
if frame < self.clip_in:
kfs.append(self.keyframes[i])
return kfs
def get_out_of_range_after_kfs(self):
# returns Keyframes before current clip start
kfs = []
for i in range(0, len(self.keyframes)):
frame, value = self.keyframes[i]
if frame > self.clip_in + self.clip_length:
kfs.append(self.keyframes[i])
return kfs
def add_keyframe(self, frame, value):
kf_index_on_frame = self.frame_has_keyframe(frame)
if kf_index_on_frame != -1:
# Trying add on top of existing keyframe makes it active
self.active_kf_index = kf_index_on_frame
return
for i in range(0, len(self.keyframes)):
kf_frame, kf_value = self.keyframes[i]
if kf_frame > frame:
#prev_frame, prev_value = self.keyframes[i - 1]
self.keyframes.insert(i, (frame, value))
self.active_kf_index = i
return
self.keyframes.append((frame, value))
self.active_kf_index = len(self.keyframes) - 1
def delete_active_keyframe(self):
if self.active_kf_index == 0:
# keyframe frame 0 cannot be removed
return
self.keyframes.pop(self.active_kf_index)
self.active_kf_index -= 1
if self.active_kf_index < 0:
self.active_kf_index = 0
self._set_pos_to_active_kf()
self.update_property_value()
updater.repaint_tline()
def set_and_display_clip_frame(self, clip_frame):
self.current_clip_frame = clip_frame
self._force_current_in_frame_range()
def _set_pos_to_active_kf(self):
frame, value = self.keyframes[self.active_kf_index]
self.current_clip_frame = frame
self._force_current_in_frame_range()
def frame_has_keyframe(self, frame):
"""
Returns index of keyframe if frame has keyframe or -1 if it doesn't.
"""
for i in range(0, len(self.keyframes)):
kf_frame, kf_value = self.keyframes[i]
if frame == kf_frame:
return i
return -1
def get_active_kf_frame(self):
frame, val = self.keyframes[self.active_kf_index]
return frame
def get_active_kf_value(self):
frame, val = self.keyframes[self.active_kf_index]
return val
def set_active_kf_value(self, new_value):
frame, val = self.keyframes.pop(self.active_kf_index)
self.keyframes.insert(self.active_kf_index,(frame, new_value))
def active_kf_pos_entered(self, frame):
if self.active_kf_index == 0:
return
prev_frame, val = self.keyframes[self.active_kf_index - 1]
prev_frame += 1
try:
next_frame, val = self.keyframes[self.active_kf_index + 1]
next_frame -= 1
except:
next_frame = self.clip_length - 1
frame = max(frame, prev_frame)
frame = min(frame, next_frame)
self.set_active_kf_frame(frame)
self.current_clip_frame = frame
def set_active_kf_frame(self, new_frame):
frame, val = self.keyframes.pop(self.active_kf_index)
self.keyframes.insert(self.active_kf_index,(new_frame, val))
def set_active_kf_frame_and_value(self, new_frame, new_value):
frame, val = self.keyframes.pop(self.active_kf_index)
self.keyframes.insert(self.active_kf_index,(new_frame, new_value))
def hack_fix_for_zero_one_keyframe_problem(self):
# This is a quick, ugly fix for bug where having volume keyframes in frames 0 and 1 mutes the whole clip.
# Look to fix underlying problem.
try:
kf1, val1 = self.keyframes[0]
kf2, val2 = self.keyframes[1]
if kf1 == 0 and kf2 == 1 and self.edit_type == VOLUME_KF_EDIT:
self.keyframes.pop(1)
except:
pass
# ------------------------------------------------- coordinates spaces
def _get_edit_area_rect(self):
x, y, w, h = self.allocation
active_width = w - 2 * END_PAD
ly = self._get_lower_y()
uy = self._get_upper_y()
return (x + END_PAD, uy, active_width - 1, ly - uy)
def _get_panel_pos(self):
return self._get_panel_pos_for_frame(self.current_clip_frame)
def _get_panel_pos_for_frame(self, clip_frame):
x, y, width, h = self.allocation
active_width = width - 2 * END_PAD
disp_frame = clip_frame - self.clip_in
return x + END_PAD + int((float(disp_frame) / float(self.clip_length)) *
float(active_width))
def _get_frame_for_panel_pos(self, panel_x):
rx, ry, rw, rh = self._get_edit_area_rect()
clip_panel_x = panel_x - rx
norm_pos = float(clip_panel_x) / float(rw)
return int(norm_pos * self.clip_length) + self.clip_in
def _get_value_for_panel_y(self, panel_y):
rx, ry, rw, rh = self._get_edit_area_rect()
editable_property = edit_data["editable_property"]
adjustment = editable_property.get_input_range_adjustment()
lower = adjustment.get_lower()
upper = adjustment.get_upper()
value_range = upper - lower
pos_fract = (ry + rh - panel_y) / rh
return pos_fract * value_range + lower
def _get_panel_y_for_value(self, value):
editable_property = edit_data["editable_property"]
adjustment = editable_property.get_input_range_adjustment()
lower = adjustment.get_lower()
upper = adjustment.get_upper()
value_range = upper - lower
value_fract = (value - lower) / value_range
return self._get_lower_y() - (self._get_lower_y() - self._get_upper_y()) * value_fract
def _get_lower_y(self):
x, y, w, h = self.allocation
return y + TOP_PAD + h - HEIGHT_PAD_PIXELS_TOTAL
def _get_upper_y(self):
x, y, w, h = self.allocation
return y + TOP_PAD
def _legalize_x(self, x):
"""
Get x in pixel range between end pads.
"""
rx, ry, rw, rh = self._get_edit_area_rect()
if x < rx:
return rx
elif x > rx + rw:
return rx + rw
else:
return x
def _legalize_y(self, y):
rx, ry, rw, rh = self._get_edit_area_rect()
if y < ry:
return ry
elif y > ry + rh:
return ry + rh
else:
return y
# ------------------------------------------------- frames
def _force_current_in_frame_range(self):
if self.current_clip_frame < self.clip_in:
self.current_clip_frame = self.clip_in
if self.current_clip_frame > self.clip_in + self.clip_length:
self.current_clip_frame = self.clip_in + self.clip_length
def _get_drag_frame(self, panel_x):
"""
Get x in range available for current drag.
"""
frame = self._get_frame_for_panel_pos(panel_x)
if frame < self.drag_min:
frame = self.drag_min
if frame > self.drag_max:
frame = self.drag_max
return frame
# ----------------------------------------------------- hit testing
def _key_frame_hit(self, x, y):
for i in range(0, len(self.keyframes)):
frame, val = self.keyframes[i]
frame_x = self._get_panel_pos_for_frame(frame)
value_y = self._get_panel_y_for_value(val)
if((abs(x - frame_x) < KF_HIT_WIDTH)
and (abs(y - value_y) < KF_HIT_WIDTH)):
return i
return None
def _area_hit(self, tx, ty, x, y, w, h):
if ty >= y and ty <= y + h: # 12 icon size
if tx >= x and tx <= x + w:
return True
return False
def _oor_start_kf_hit(self, x, y):
rx, ry, rw, rh = self.allocation
kfy = self._get_lower_y() + KF_LOWER_OFF
area_y = kfy + KF_ICON_Y_PAD
area_x = rx + OUT_OF_RANGE_ICON_PAD - OUT_OF_RANGE_KF_ICON_HALF * 2
return self._area_hit(x, y, area_x, area_y, 12, 12)
def _oor_end_kf_hit(self, x, y):
rx, ry, rw, rh = self.allocation
kfy = self._get_lower_y() + KF_LOWER_OFF
area_x = rx + rw - OUT_OF_RANGE_ICON_PAD
area_y = kfy + KF_ICON_Y_PAD
return self._area_hit(x, y, area_x, area_y, 12, 12)
def _hamburger_hit(self, x, y):
rx, ry, rw, rh = self.allocation
return self._area_hit(x, y, rx + 4.5, ry + 4, 12, 12)
def overlay_area_hit(self, tx, ty):
x, y, w, h = self.allocation
if tx >= x and tx <= x + w:
if ty >= y and ty <= y + h:
return True
return False
# ------------------------------------------------------------ menus
def _show_oor_before_menu(self, widget, event):
menu = oor_before_menu
self._build_oor_before_menu(menu)
menu.popup(None, None, None, None, event.button, event.time)
def _build_oor_before_menu(self, menu):
guiutils.remove_children(menu)
before_kfs = len(self.get_out_of_range_before_kfs())
if before_kfs == 0 or (before_kfs == 1 and len(self.keyframes) == 1):
item = self._get_menu_item(_("No Edit Actions currently available"), self._oor_menu_item_activated, "noop" )
item.set_sensitive(False)
menu.add(item)
if before_kfs > 1:
menu.add(self._get_menu_item(_("Delete all but first Keyframe before Clip Range"), self._oor_menu_item_activated, "delete_all_before" ))
sep = Gtk.SeparatorMenuItem()
sep.show()
menu.add(sep)
if len(self.keyframes) > 1 and before_kfs > 0:
menu.add(self._get_menu_item(_("Set Keyframe at Frame 0 to value of next Keyframe"), self._oor_menu_item_activated, "zero_next" ))
def _show_oor_after_menu(self, widget, event):
menu = oor_before_menu
self._build_oor_after_menu(menu)
menu.popup(None, None, None, None, event.button, event.time)
def _build_oor_after_menu(self, menu):
guiutils.remove_children(menu)
after_kfs = len(self.get_out_of_range_after_kfs())
if after_kfs == 0:
item = self._get_menu_item(_("No Edit Actions currently available"), self._oor_menu_item_activated, "noop" )
item.set_sensitive(False)
menu.add(item)
return
if self.edit_type == BRIGHTNESS_KF_EDIT:
menu.add(self._get_menu_item(_("Delete all Keyframes after Clip Range"), self._oor_menu_item_activated, "delete_all_after" ))
elif self.edit_type == VOLUME_KF_EDIT:
if after_kfs > 1:
menu.add(self._get_menu_item(_("Delete all but last Keyframe after Clip Range"), self._oor_menu_item_activated, "delete_all_but_last_after" ))
else:
item = self._get_menu_item(_("No Edit Actions currently available"), self._oor_menu_item_activated, "noop" )
item.set_sensitive(False)
menu.add(item)
def _show_hamburger_menu(self, widget, event):
menu = hamburger_menu
guiutils.remove_children(menu)
if edit_data["track"].type == appconsts.VIDEO:
if edit_data["clip"].media_type == appconsts.VIDEO:
edit_volume = self._get_menu_item(_("Edit Volume Keyframes"), self._oor_menu_item_activated, "edit_volume" )
if self.edit_type == VOLUME_KF_EDIT:
edit_volume.set_sensitive(False)
menu.add(edit_volume)
edit_brightness = self._get_menu_item(_("Edit Brightness Keyframes"), self._oor_menu_item_activated, "edit_brightness" )
if self.edit_type == BRIGHTNESS_KF_EDIT:
edit_brightness.set_sensitive(False)
menu.add(edit_brightness)
editable_params_exist = False
params_menu_item = Gtk.MenuItem(_("Edit Other Filter Parameters"))
params_menu = Gtk.Menu()
guiutils.remove_children(params_menu)
for i in range(0, len(edit_data["clip"].filters)):
filt = edit_data["clip"].filters[i]
for prop in filt.properties:
p_name, p_val, p_type = prop
args_str = filt.info.property_args[p_name]
args_dict = propertyparse.args_string_to_args_dict(args_str)
try:
editor = args_dict["editor"]
except:
editor = "slider"
try:
disp_name = args_dict["displayname"]
except:
disp_name = p_name
try:
range_in = args_dict["range_in"]
except:
range_in = None
if (editor == "slider" or editor == "keyframe_editor") and range_in != None:
param_data = (p_name, filt, i, disp_name)
editable_params_exist = True
item_text = filt.info.name + ": " + disp_name
param_item = self._get_menu_item(item_text, self._param_edit_item_activated, param_data)
params_menu.add(param_item)
if editable_params_exist == False:
params_menu_item.set_sensitive(False)
params_menu_item.set_submenu(params_menu)
params_menu_item.show_all()
menu.add(params_menu_item)
sep = Gtk.SeparatorMenuItem()
sep.show()
menu.add(sep)
leading_menu_item = Gtk.MenuItem(_("Leading Keyframes"))
leading_menu = Gtk.Menu()
self._build_oor_before_menu(leading_menu)
leading_menu_item.set_submenu(leading_menu)
leading_menu_item.show_all()
menu.add(leading_menu_item)
trailing_menu_item = Gtk.MenuItem(_("Trailing Keyframes"))
trailing_menu = Gtk.Menu()
self._build_oor_after_menu(trailing_menu)
trailing_menu_item.set_submenu(trailing_menu)
trailing_menu_item.show_all()
menu.add(trailing_menu_item)
sep = Gtk.SeparatorMenuItem()
sep.show()
menu.add(sep)
playhead_follow_item = Gtk.CheckMenuItem()
playhead_follow_item.set_label(_("Playhead Follows Dragged Keyframe"))
playhead_follow_item.set_active(_playhead_follow_kf)
playhead_follow_item.connect("activate", self._oor_menu_item_activated, "playhead_follows")
playhead_follow_item.show()
menu.add(playhead_follow_item)
value_snapping_item = Gtk.MenuItem(_("Value Snapping"))
value_snapping_menu = Gtk.Menu()
guiutils.remove_children(value_snapping_menu)
first_item = Gtk.RadioMenuItem()
first_item.set_label("1")
if _snapping == 1:
first_item.set_active(True)
first_item.show()
value_snapping_menu.append(first_item)
first_item.connect("activate", self._oor_menu_item_activated, "1")
radio_item = Gtk.RadioMenuItem.new_with_label([first_item], "2")
if _snapping == 2:
radio_item.set_active(True)
radio_item.connect("activate", self._oor_menu_item_activated, "2")
value_snapping_menu.append(radio_item)
radio_item.show()
radio_item = Gtk.RadioMenuItem.new_with_label([first_item], "5")
if _snapping == 5:
radio_item.set_active(True)
radio_item.connect("activate", self._oor_menu_item_activated, "5")
value_snapping_menu.append(radio_item)
radio_item.show()
value_snapping_item.set_submenu(value_snapping_menu)
value_snapping_item.show_all()
menu.add(value_snapping_item)
sep = Gtk.SeparatorMenuItem()
sep.show()
menu.add(sep)
menu.add(self._get_menu_item(_("Exit Edit"), self._oor_menu_item_activated, "exit" ))
menu.popup(None, None, None, None, event.button, event.time)
def _oor_menu_item_activated(self, widget, data):
global _snapping
if data == "delete_all_before":
keep_doing = True
while keep_doing:
try:
frame, value = self.keyframes[1]
if frame < self.clip_in:
self.keyframes.pop(1)
else:
keep_doing = False
except:
keep_doing = False
elif data == "delete_all_but_last_after":
keep_doing = True
index = 1
while keep_doing:
try:
frame, value = self.keyframes[index]
if frame > self.clip_in + self.clip_length and index < (len(self.keyframes) - 1):
self.keyframes.pop(index)
else:
index += 1
except:
keep_doing = False
elif data == "zero_next":
frame_zero, frame_zero_value = self.keyframes[0]
frame, value = self.keyframes[1]
self.keyframes.pop(0)
self.keyframes.insert(0, (frame_zero, value))
self.update_property_value()
elif data == "delete_all_after":
delete_done = False
for i in range(0, len(self.keyframes)):
frame, value = self.keyframes[i]
if frame > self.clip_in + self.clip_length:
self.keyframes.pop(i)
popped = True
while popped:
try:
self.keyframes.pop(i)
except:
popped = False
delete_done = True
if delete_done:
break
elif data == "edit_brightness":
init_tool_for_clip(edit_data["clip"] , edit_data["track"], BRIGHTNESS_KF_EDIT)
elif data == "edit_volume":
init_tool_for_clip(edit_data["clip"] , edit_data["track"], VOLUME_KF_EDIT)
elif data == "exit":
set_no_clip_edit_data()
elif data == "playhead_follows":
global _playhead_follow_kf
_playhead_follow_kf = widget.get_active()
elif data == "1":
_snapping = 1
elif data == "2":
_snapping = 2
elif data == "5":
_snapping = 5
updater.repaint_tline()
def _param_edit_item_activated(self, widget, data):
init_tool_for_clip(edit_data["clip"], edit_data["track"], PARAM_KF_EDIT, data)
def _get_menu_item(self, text, callback, data):
item = Gtk.MenuItem(text)
item.connect("activate", callback, data)
item.show()
return item
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/launch/ 0000775 0000000 0000000 00000000000 13610327166 0024355 5 ustar 00root root 0000000 0000000 flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/launch/flowbladeaudiorender 0000664 0000000 0000000 00000001036 13610327166 0030461 0 ustar 00root root 0000000 0000000 #!/usr/bin/python3
import sys
import os
modules_path = os.path.dirname(os.path.abspath(sys.argv[0])).rstrip("/launch")
root_path = os.path.dirname(os.path.abspath(sys.argv[0])).rstrip("/Flowblade/launch") # TODO: THIS NEEDS TO BE CONDITIONAL ON BEING FILE SYSTEM INSTALLATION!!
sys.path.insert(0, modules_path)
sys.path.insert(0, root_path) # TODO: THIS NEEDS TO BE CONDITIONAL ON BEING FILE SYSTEM INSTALLATION!!
import processutils
processutils.update_sys_path(modules_path)
import audiowaveformrenderer
audiowaveformrenderer.main()
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/launch/flowbladebatch 0000775 0000000 0000000 00000000425 13610327166 0027245 0 ustar 00root root 0000000 0000000 #!/usr/bin/python3
import sys
import os
modules_path = os.path.dirname(os.path.abspath(sys.argv[0])).rstrip("/launch")
sys.path.insert(0, modules_path)
import processutils
processutils.update_sys_path(modules_path)
import batchrendering
batchrendering.main(modules_path)
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/launch/flowbladeclapperless 0000775 0000000 0000000 00000000403 13610327166 0030475 0 ustar 00root root 0000000 0000000 #!/usr/bin/python3
import sys
import os
modules_path = os.path.dirname(os.path.abspath(sys.argv[0])).rstrip("/launch")
sys.path.insert(0, modules_path)
import processutils
processutils.update_sys_path(modules_path)
import clapperless
clapperless.main()
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/launch/flowbladegmic 0000775 0000000 0000000 00000001356 13610327166 0027107 0 ustar 00root root 0000000 0000000 #!/usr/bin/python3
import sys
import os
modules_path = os.path.dirname(os.path.abspath(sys.argv[0])).rstrip("/launch")
sys.path.insert(0, modules_path)
import processutils
processutils.update_sys_path(modules_path)
try:
import gmic
import editorstate # Used to decide which translations from file system are used
root_dir = modules_path.split("/")[1]
if root_dir != "home":
editorstate.app_running_from = editorstate.RUNNING_FROM_INSTALLATION
else:
editorstate.app_running_from = editorstate.RUNNING_FROM_DEV_VERSION
except Exception as err:
print ("Failed to import gmic")
print ("ERROR:", err)
print ("Installation was assumed to be at:", modules_path)
sys.exit(1)
gmic.main(modules_path)
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/launch/flowblademediaimport 0000775 0000000 0000000 00000001546 13610327166 0030503 0 ustar 00root root 0000000 0000000 #!/usr/bin/python3
import sys
import os
modules_path = os.path.dirname(os.path.abspath(sys.argv[0])).rstrip("/launch")
sys.path.insert(0, modules_path)
import processutils
processutils.update_sys_path(modules_path)
try:
import projectmediaimport
import editorstate # Used to decide which translations from file system are used
root_dir = modules_path.split("/")[1]
if root_dir != "home":
editorstate.app_running_from = editorstate.RUNNING_FROM_INSTALLATION
else:
editorstate.app_running_from = editorstate.RUNNING_FROM_DEV_VERSION
except Exception:
print ("Failed to import projectmediaimport")
print ("ERROR:", err)
print ("Installation was assumed to be at:", modules_path)
sys.exit(1)
projectmediaimport.main(modules_path, sys.argv[1]) # sys.argv[1] is a file path to project that is the media import target
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/launch/flowblademedialinker 0000775 0000000 0000000 00000001531 13610327166 0030447 0 ustar 00root root 0000000 0000000 #!/usr/bin/python3
import sys
import os
modules_path = os.path.dirname(os.path.abspath(sys.argv[0])).rstrip("/launch")
sys.path.insert(0, modules_path)
import processutils
processutils.update_sys_path(modules_path)
try:
import medialinker
import editorstate # Used to decide which translations from file system are used
root_dir = modules_path.split("/")[1]
if root_dir != "home":
editorstate.app_running_from = editorstate.RUNNING_FROM_INSTALLATION
else:
editorstate.app_running_from = editorstate.RUNNING_FROM_DEV_VERSION
except Exception as err:
print ("Failed to import medialinker")
print ("ERROR:", err)
print ("Installation was assumed to be at:", modules_path)
sys.exit(1)
medialinker.main(modules_path, sys.argv[1]) # sys.argv[1] is possibly a file path to project to be opened at startup
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/launch/flowbladephantom 0000775 0000000 0000000 00000000214 13610327166 0027626 0 ustar 00root root 0000000 0000000 #!/usr/bin/env bash
# Get lanch data
JAR_PATH=$1
# Launch Phantom
echo "rrr"
echo "jarpath:"$JAR_PATH
echo "$@"
java -jar $JAR_PATH "$@"
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/launch/flowbladesinglerender 0000664 0000000 0000000 00000000443 13610327166 0030642 0 ustar 00root root 0000000 0000000 #!/usr/bin/python3
import sys
import os
modules_path = os.path.dirname(os.path.abspath(sys.argv[0])).rstrip("/launch")
sys.path.insert(0, modules_path)
import processutils
processutils.update_sys_path(modules_path)
import batchrendering
batchrendering.single_render_main(modules_path)
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/locale/ 0000775 0000000 0000000 00000000000 13610327166 0024342 5 ustar 00root root 0000000 0000000 flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/locale/Flowblade/ 0000775 0000000 0000000 00000000000 13610327166 0026241 5 ustar 00root root 0000000 0000000 flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/locale/Flowblade/flowblade.pot 0000664 0000000 0000000 00000402242 13610327166 0030730 0 ustar 00root root 0000000 0000000 # SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-12-12 18:20+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
#: app.py:193
msgid "No Autosave"
msgstr ""
#: app.py:193
msgid "1 min"
msgstr ""
#: app.py:193
msgid "2 min"
msgstr ""
#: app.py:193
msgid "5 min"
msgstr ""
#: app.py:903
msgid "Too small screen for this application."
msgstr ""
#: app.py:906
msgid "Minimum screen dimensions for this application are 1152 x 768.\n"
msgstr ""
#: app.py:907
msgid "Your screen dimensions are "
msgstr ""
#: app.py:940 projectaction.py:401 projectaction.py:724
msgid "Project has not been saved previously"
msgstr ""
#: app.py:941 projectaction.py:402 projectaction.py:725
msgid "Save project with File -> Save As before closing."
msgstr ""
#: projectaction.py:133
msgid "Media asset was missing!"
msgstr ""
#: projectaction.py:134
msgid "Path of missing asset:"
msgstr ""
#: projectaction.py:135
msgid ""
"Relative search for replacement file in sub folders of project file failed."
msgstr ""
#: projectaction.py:136
msgid "To load the project you will need to either:"
msgstr ""
#: projectaction.py:137
msgid ""
"Open project in 'Media Relinker' tool to relink media assets to new files, or"
msgstr ""
#: projectaction.py:138
msgid "Place a file with the same exact name and path on the hard drive"
msgstr ""
#: projectaction.py:139
msgid "Open project in Media Relinker tool"
msgstr ""
#: projectaction.py:158
msgid "Profile with Description: '"
msgstr ""
#: projectaction.py:158
msgid "' was not found on load!"
msgstr ""
#: projectaction.py:159
msgid ""
"It is possible to load the project by creating a User Profile with exactly "
"the same Description\n"
"as the missing profile. "
msgstr ""
#: projectaction.py:160
msgid "User Profiles can be created by selecting 'Edit->Profiles Manager'."
msgstr ""
#: projectaction.py:167
msgid "Opening"
msgstr ""
#: projectaction.py:307
msgid "Media files already present in project were opened!"
msgstr ""
#: projectaction.py:313
msgid ""
"Files already present:\n"
"\n"
msgstr ""
#: projectaction.py:320
msgid ""
"\n"
"No duplicate media items were added to project."
msgstr ""
#: projectaction.py:519 exporting.py:516
msgid "Selected folder contains files"
msgstr ""
#: projectaction.py:520
msgid ""
"When saving a back-up snapshot of the project, the selected folder\n"
"has to be empty."
msgstr ""
#: projectaction.py:577
msgid "Copying project media assets"
msgstr ""
#: projectaction.py:578
msgid "Saving project file"
msgstr ""
#: projectaction.py:737
msgid "Project not found on disk"
msgstr ""
#: projectaction.py:738
msgid "Project can't be loaded."
msgstr ""
#: projectaction.py:746
msgid "Project has not been saved since it was opened."
msgstr ""
#: projectaction.py:751
msgid "Project was saved less than a minute ago."
msgstr ""
#: projectaction.py:754
msgid "Project was saved one minute ago."
msgstr ""
#: projectaction.py:756
msgid "Project was saved "
msgstr ""
#: projectaction.py:756
msgid " minutes ago."
msgstr ""
#: projectaction.py:768
msgid "Render target file exists!"
msgstr ""
#: projectaction.py:769
msgid "Confirm overwriting existing file."
msgstr ""
#: projectaction.py:775
msgid "Project is currently using proxy media!"
msgstr ""
#: projectaction.py:776
msgid ""
"Rendering from proxy media will produce worse quality than rendering from "
"original media.\n"
"Convert to using original media in Proxy Manager for best quality.\n"
"\n"
"Select 'Confirm' to render from proxy media anyway."
msgstr ""
#: projectaction.py:864
msgid "Render launch failed!"
msgstr ""
#: projectaction.py:865 projectaction.py:879 tools/batchrendering.py:307
msgid "Error message: "
msgstr ""
#: projectaction.py:878
msgid "Adding item to render queue failed!"
msgstr ""
#: projectaction.py:891
msgid "Render Proxy Files For Selected Media"
msgstr ""
#: projectaction.py:893
msgid "Select All"
msgstr ""
#: projectaction.py:894
msgid "Select None"
msgstr ""
#: projectaction.py:896
msgid "Move Selected Media To Bin"
msgstr ""
#: projectaction.py:899
msgid "No Target Bins"
msgstr ""
#: projectaction.py:918
msgid "Append All Media to Timeline"
msgstr ""
#: projectaction.py:919
msgid "Append Selected Media to Timeline"
msgstr ""
#: projectaction.py:948 editorwindow.py:238
msgid "Add Video, Audio or Image..."
msgstr ""
#: projectaction.py:949 editorwindow.py:239
msgid "Add Image Sequence..."
msgstr ""
#: projectaction.py:970
msgid "Open.."
msgstr ""
#: projectaction.py:999
msgid "Opening .mlt or .xml file as media was disallowed!"
msgstr ""
#: projectaction.py:1000
msgid ""
"Only XML files with matching Profiles can be opened as clips.\n"
"\n"
"Last non-matching MLT XML file had Profile: "
msgstr ""
#: projectaction.py:1020
msgid "No file was selected"
msgstr ""
#: projectaction.py:1020
msgid "Select a numbered file to add an Image Sequence to Project."
msgstr ""
#: projectaction.py:1028
msgid "Not a sequence file!"
msgstr ""
#: projectaction.py:1028
msgid ""
"Selected file does not have a number part in it,\n"
"so it can't be an image sequence file."
msgstr ""
#: projectaction.py:1196 projectaction.py:1198 projectaction.py:1207
#: projectaction.py:1215 projectaction.py:1222 tools/batchrendering.py:1028
#: clipmenuaction.py:156 clipmenuaction.py:158
msgid "N/A"
msgstr ""
#: projectaction.py:1211 guicomponents.py:2247
msgid "Yes"
msgstr ""
#: projectaction.py:1213 guicomponents.py:2249
msgid "No"
msgstr ""
#: projectaction.py:1268
msgid "Select Project for Media Import"
msgstr ""
#: projectaction.py:1288
msgid "selection_"
msgstr ""
#: projectaction.py:1289
msgid "Save Selection Compound Clip"
msgstr ""
#: projectaction.py:1343 projectaction.py:1362 projectaction.py:1677
#: projectaction.py:1693 projectdata.py:219 tlineaction.py:274
msgid "sequence_"
msgstr ""
#: projectaction.py:1344 projectaction.py:1363
msgid "Save Sequence Compound Clip"
msgstr ""
#: projectaction.py:1387
msgid "frame_"
msgstr ""
#: projectaction.py:1388
msgid "Save Freeze Frame Sequence Compound Clip"
msgstr ""
#: projectaction.py:1467 editorwindow.py:242
msgid "Add Bin"
msgstr ""
#: projectaction.py:1468 editorwindow.py:243
msgid "Delete Selected Bin"
msgstr ""
#: projectaction.py:1472
msgid "Move Bin"
msgstr ""
#: projectaction.py:1474 editorwindow.py:976
msgid "Up"
msgstr ""
#: projectaction.py:1475 editorwindow.py:980
msgid "Down"
msgstr ""
#: projectaction.py:1476
msgid "First"
msgstr ""
#: projectaction.py:1477
msgid "Last"
msgstr ""
#: projectaction.py:1536
msgid "Can't remove a non-empty bin"
msgstr ""
#: projectaction.py:1537
msgid "You must remove all files from the bin before deleting it."
msgstr ""
#: projectaction.py:1545
msgid "Can't remove last bin"
msgstr ""
#: projectaction.py:1546
msgid "There must always exist at least one bin."
msgstr ""
#: projectaction.py:1642
msgid "Selected sequence is already being edited"
msgstr ""
#: projectaction.py:1643
msgid ""
"Select another sequence. Press Add -button to create a\n"
"new sequence if needed."
msgstr ""
#: projectaction.py:1657 editorwindow.py:245
msgid "Add New Sequence"
msgstr ""
#: projectaction.py:1658 editorwindow.py:246
msgid "Edit Selected Sequence"
msgstr ""
#: projectaction.py:1659 editorwindow.py:247
msgid "Delete Selected Sequence"
msgstr ""
#: projectaction.py:1661
msgid "Create Compound Clip from Selected Sequence"
msgstr ""
#: projectaction.py:1732
msgid ""
"Are you sure you want to delete\n"
"sequence '"
msgstr ""
#: projectaction.py:1732
msgid "'?"
msgstr ""
#: projectaction.py:1733
msgid "This operation can not be undone. Sequence will be permanently lost."
msgstr ""
#: projectaction.py:1759
msgid "Can't remove last sequence"
msgstr ""
#: projectaction.py:1760
msgid "There must always exist at least one sequence."
msgstr ""
#: editorwindow.py:191
msgid "_File"
msgstr ""
#: editorwindow.py:192
msgid "_New..."
msgstr ""
#: editorwindow.py:193
msgid "_Open..."
msgstr ""
#: editorwindow.py:194
msgid "Open Recent"
msgstr ""
#: editorwindow.py:195
msgid "_Save"
msgstr ""
#: editorwindow.py:196
msgid "_Save As..."
msgstr ""
#: editorwindow.py:197
msgid "Save Backup Snapshot..."
msgstr ""
#: editorwindow.py:198 dialogs.py:274 dialogs.py:391 dialogs.py:1275
msgid "Export"
msgstr ""
#: editorwindow.py:199
msgid "MLT XML"
msgstr ""
#: editorwindow.py:200
msgid "EDL"
msgstr ""
#: editorwindow.py:201
msgid "Current Frame"
msgstr ""
#: editorwindow.py:202
msgid "Current Sequence Audio As Ardour Session"
msgstr ""
#: editorwindow.py:203
msgid "_Close"
msgstr ""
#: editorwindow.py:204
msgid "_Quit"
msgstr ""
#: editorwindow.py:205
msgid "_Edit"
msgstr ""
#: editorwindow.py:206
msgid "_Undo"
msgstr ""
#: editorwindow.py:207
msgid "_Redo"
msgstr ""
#: editorwindow.py:208
msgid "Copy"
msgstr ""
#: editorwindow.py:209
msgid "Paste"
msgstr ""
#: editorwindow.py:210
msgid "Paste Filters / Properties"
msgstr ""
#: editorwindow.py:211
msgid "Add Monitor Clip"
msgstr ""
#: editorwindow.py:212 shortcuts.py:190
msgid "Append"
msgstr ""
#: editorwindow.py:213 shortcuts.py:189 shortcuts.py:206 workflow.py:65
msgid "Insert"
msgstr ""
#: editorwindow.py:214
msgid "Three Point Overwrite"
msgstr ""
#: editorwindow.py:215
msgid "Range Overwrite"
msgstr ""
#: editorwindow.py:216
msgid "Cut Clip"
msgstr ""
#: editorwindow.py:217
msgid "Split to new Sequence at Playhead Position"
msgstr ""
#: editorwindow.py:218 dialogs.py:1458 guicomponents.py:1916
#: translations.py:541 workflow.py:202
msgid "Lift"
msgstr ""
#: editorwindow.py:219 dialogs.py:1457 workflow.py:202
msgid "Splice Out"
msgstr ""
#: editorwindow.py:220 guicomponents.py:1490 guicomponents.py:1613
msgid "Resync"
msgstr ""
#: editorwindow.py:221
msgid "Set Sync Parent"
msgstr ""
#: editorwindow.py:222
msgid "Add Single Track Transition"
msgstr ""
#: editorwindow.py:223
msgid "Add Single Track Fade"
msgstr ""
#: editorwindow.py:224 guicomponents.py:1531 guicomponents.py:1584
msgid "Clear Filters"
msgstr ""
#: editorwindow.py:225 dialogs.py:1479
msgid "Timeline"
msgstr ""
#: editorwindow.py:226
msgid "All Filters Off"
msgstr ""
#: editorwindow.py:227
msgid "All Filters On"
msgstr ""
#: editorwindow.py:228
msgid "Sync All Compositors"
msgstr ""
#: editorwindow.py:229
msgid "Set Compositor Auto Fades..."
msgstr ""
#: editorwindow.py:230
msgid "Change Sequence Tracks Count..."
msgstr ""
#: editorwindow.py:231
msgid "Watermark..."
msgstr ""
#: editorwindow.py:232 diskcachemanagement.py:149
msgid "Disk Cache Manager"
msgstr ""
#: editorwindow.py:233 profilesmanager.py:46
msgid "Profiles Manager"
msgstr ""
#: editorwindow.py:234
msgid "Preferences"
msgstr ""
#: editorwindow.py:235 preferenceswindow.py:66
msgid "View"
msgstr ""
#: editorwindow.py:236
msgid "Fullscreen"
msgstr ""
#: editorwindow.py:237 editorwindow.py:641 projectinfogui.py:69
msgid "Project"
msgstr ""
#: editorwindow.py:240
msgid "Create Color Clip..."
msgstr ""
#: editorwindow.py:241
msgid "Bin"
msgstr ""
#: editorwindow.py:244
msgid "Sequence"
msgstr ""
#: editorwindow.py:248
msgid "Compositing Mode"
msgstr ""
#: editorwindow.py:249
msgid "Create Pattern Producer"
msgstr ""
#: editorwindow.py:250 translations.py:423 patternproducer.py:67
msgid "Noise"
msgstr ""
#: editorwindow.py:251 patternproducer.py:72
msgid "EBU Bars"
msgstr ""
#: editorwindow.py:252 patternproducer.py:81
msgid "Ising"
msgstr ""
#: editorwindow.py:253 patternproducer.py:98
msgid "Color Pulse"
msgstr ""
#: editorwindow.py:254 patternproducer.py:114
msgid "Count"
msgstr ""
#: editorwindow.py:255
msgid "Create Compound Clip"
msgstr ""
#: editorwindow.py:256
msgid "From Selected Clips"
msgstr ""
#: editorwindow.py:257
msgid "From Current Sequence"
msgstr ""
#: editorwindow.py:258
msgid "From Current Sequence With Freeze Frame at Playhead Position"
msgstr ""
#: editorwindow.py:259
msgid "Audio Sync Merge Clip From 2 Media Items "
msgstr ""
#: editorwindow.py:260
msgid "Import Media From Project..."
msgstr ""
#: editorwindow.py:261
msgid "Import Another Sequence Into This Sequence..."
msgstr ""
#: editorwindow.py:262 dialogs.py:1445 shortcuts.py:195
msgid "Log Marked Clip Range"
msgstr ""
#: editorwindow.py:263
msgid "View Project Events..."
msgstr ""
#: editorwindow.py:264
msgid "Recreate Media Icons..."
msgstr ""
#: editorwindow.py:265
msgid "Remove Unused Media..."
msgstr ""
#: editorwindow.py:266
msgid "Change Project Profile..."
msgstr ""
#: editorwindow.py:267 proxyediting.py:219
msgid "Proxy Manager"
msgstr ""
#: editorwindow.py:268
msgid "Project Info"
msgstr ""
#: editorwindow.py:269 editorwindow.py:642 rendergui.py:177 rendergui.py:323
#: tools/batchrendering.py:891 tools/gmic.py:816
msgid "Render"
msgstr ""
#: editorwindow.py:270
msgid "Add To Batch Render Queue..."
msgstr ""
#: editorwindow.py:271 middlebar.py:187
msgid "Batch Render Queue"
msgstr ""
#: editorwindow.py:272
msgid "Rerender All Rendered Transitions And Fades "
msgstr ""
#: editorwindow.py:273
msgid "Render Timeline"
msgstr ""
#: editorwindow.py:274 dialogs.py:1506
msgid "Tools"
msgstr ""
#: editorwindow.py:275 middlebar.py:187 tools/titler.py:192
msgid "Titler"
msgstr ""
#: editorwindow.py:276 middlebar.py:187
msgid "Audio Mixer"
msgstr ""
#: editorwindow.py:277 tools/gmic.py:876 toolsintegration.py:108
msgid "G'MIC Effects"
msgstr ""
#: editorwindow.py:278 medialinker.py:181
msgid "Media Relinker"
msgstr ""
#: editorwindow.py:279
msgid "_Help"
msgstr ""
#: editorwindow.py:280
msgid "Contents"
msgstr ""
#: editorwindow.py:281 dialogs.py:617
msgid "Runtime Environment"
msgstr ""
#: editorwindow.py:282 dialogs.py:1356
msgid "Keyboard Shortcuts"
msgstr ""
#: editorwindow.py:283 dialogs.py:491
msgid "About"
msgstr ""
#: editorwindow.py:633
msgid "Media"
msgstr ""
#: editorwindow.py:637
msgid "Range Log"
msgstr ""
#: editorwindow.py:638 clipeffectseditor.py:139
msgid "Filters"
msgstr ""
#: editorwindow.py:639
msgid "Compositors"
msgstr ""
#: editorwindow.py:659
msgid "Prev Frame - Arrow Left"
msgstr ""
#: editorwindow.py:659
msgid "Next Frame - Arrow Right"
msgstr ""
#: editorwindow.py:659
msgid "Play - Space"
msgstr ""
#: editorwindow.py:659
msgid "Stop - Space"
msgstr ""
#: editorwindow.py:659
msgid "Mark In - I"
msgstr ""
#: editorwindow.py:659
msgid "Mark Out - O"
msgstr ""
#: editorwindow.py:659
msgid "Clear Marks"
msgstr ""
#: editorwindow.py:659 dialogs.py:1498
msgid "To Mark In"
msgstr ""
#: editorwindow.py:659 dialogs.py:1499
msgid "To Mark Out"
msgstr ""
#: editorwindow.py:926
msgid "Window Mode"
msgstr ""
#: editorwindow.py:929 preferenceswindow.py:365
msgid "Single Window"
msgstr ""
#: editorwindow.py:933 preferenceswindow.py:366
msgid "Two Windows"
msgstr ""
#: editorwindow.py:947
msgid "Middlebar Layout"
msgstr ""
#: editorwindow.py:950
msgid "Timecode Left"
msgstr ""
#: editorwindow.py:954
msgid "Timecode Center"
msgstr ""
#: editorwindow.py:958
msgid "Components Centered"
msgstr ""
#: editorwindow.py:973
msgid "Tabs Position"
msgstr ""
#: editorwindow.py:998
msgid "Monitor Playback Interpolation"
msgstr ""
#: editorwindow.py:1002
msgid "Nearest Neighbour (fast)"
msgstr ""
#: editorwindow.py:1006
msgid "Bilinear (good)"
msgstr ""
#: editorwindow.py:1010
msgid "Bicubic (better)"
msgstr ""
#: editorwindow.py:1015
msgid "Hyper/Lanczos (best)"
msgstr ""
#: editorwindow.py:1025 shortcuts.py:214
msgid "Zoom In"
msgstr ""
#: editorwindow.py:1028 shortcuts.py:213
msgid "Zoom Out"
msgstr ""
#: editorwindow.py:1031
msgid "Zoom Fit"
msgstr ""
#: editorwindow.py:1041 workflow.py:224
msgid "Top Down Free Move"
msgstr ""
#: editorwindow.py:1045 workflow.py:224
msgid "Top Down Auto Follow"
msgstr ""
#: editorwindow.py:1049 workflow.py:224
msgid "Standard Auto Follow"
msgstr ""
#: editorwindow.py:1073
msgid "Global Window Mode changed"
msgstr ""
#: editorwindow.py:1074
msgid "Application restart required for the new layout choice to take effect."
msgstr ""
#: editorwindow.py:1168
msgid "Timeline current frame timecode"
msgstr ""
#: editorwindow.py:1170
msgid "Select view mode: Video / Vectorscope/ RGBParade"
msgstr ""
#: editorwindow.py:1171
msgid "Set trim view and match frames"
msgstr ""
#: editorwindow.py:1173
msgid "Sequence / Media current position"
msgstr ""
#: clipeffectseditor.py:75
msgid "Clip Filters Stack"
msgstr ""
#: clipeffectseditor.py:138
msgid "Stack"
msgstr ""
#: clipeffectseditor.py:156
msgid "Select Filter Group"
msgstr ""
#: clipeffectseditor.py:157
msgid "Current group Filters"
msgstr ""
#: clipeffectseditor.py:263
msgid "Quit editing Clip in editor"
msgstr ""
#: clipeffectseditor.py:294
msgid "Clip being edited"
msgstr ""
#: clipeffectseditor.py:295
msgid "Clip Filter Stack"
msgstr ""
#: clipeffectseditor.py:296
msgid "Add Filter to Clip Filter Stack"
msgstr ""
#: clipeffectseditor.py:297
msgid "Delete Filter from Clip Filter Stack"
msgstr ""
#: clipeffectseditor.py:298
msgid "Toggle all Filters On/Off"
msgstr ""
#: clipeffectseditor.py:486
msgid "No Clip"
msgstr ""
#: clipeffectseditor.py:492
msgid "Clip Has No Filters"
msgstr ""
#: clipeffectseditor.py:588
msgid "No editable parameters"
msgstr ""
#: clipeffectseditor.py:676
msgid "_effect_values"
msgstr ""
#: clipeffectseditor.py:710
msgid "Saved Filter data not applicaple for this Filter!"
msgstr ""
#: clipeffectseditor.py:711 compositeeditor.py:327
msgid "Saved data is for "
msgstr ""
#: clipeffectseditor.py:711
msgid "current edited Filter is "
msgstr ""
#: compositeeditor.py:69 compositeeditor.py:161
msgid "No Compositor"
msgstr ""
#: compositeeditor.py:196
msgid "Destination Track:"
msgstr ""
#: compositeeditor.py:296
msgid "_compositor_values"
msgstr ""
#: compositeeditor.py:326
msgid "Saved Compositor data not applicaple for this compositor!"
msgstr ""
#: compositeeditor.py:327
msgid ", current compositor is "
msgstr ""
#: dialogs.py:55 dialogs.py:94
msgid "New Project"
msgstr ""
#: dialogs.py:57 dialogs.py:125 dialogs.py:183 dialogs.py:236 dialogs.py:273
#: dialogs.py:307 dialogs.py:324 dialogs.py:349 dialogs.py:373 dialogs.py:390
#: dialogs.py:405 dialogs.py:436 dialogs.py:459 dialogs.py:481 dialogs.py:862
#: dialogs.py:957 dialogs.py:991 dialogs.py:1012 dialogs.py:1053
#: dialogs.py:1081 dialogs.py:1109 dialogs.py:1235 dialogs.py:1274
#: dialogs.py:1288 dialogs.py:1302 dialogs.py:1316 dialogs.py:1330
#: dialogs.py:1344 dialogs.py:1359 dialogs.py:1616 dialogs.py:1733
#: dialogs.py:1781 dialogs.py:1896 dialogs.py:1932 propertyeditorbuilder.py:531
#: propertyeditorbuilder.py:670 propertyeditorbuilder.py:710 rendergui.py:49
#: rendergui.py:108 rendergui.py:122 rendergui.py:139 rendergui.py:1069
#: preferenceswindow.py:49 tools/batchrendering.py:989 proxyediting.py:432
#: tlineaction.py:1475 patternproducer.py:354 patternproducer.py:387
#: patternproducer.py:418 tools/gmic.py:402 tools/gmic.py:426 tools/gmic.py:555
#: dialogutils.py:98 dialogutils.py:158 exporting.py:436
msgid "Cancel"
msgstr ""
#: dialogs.py:58 dialogs.py:237 dialogs.py:308 dialogs.py:374 dialogs.py:493
#: dialogs.py:619 dialogs.py:740 dialogs.py:753 dialogs.py:1617
#: propertyeditorbuilder.py:532 propertyeditorbuilder.py:671
#: propertyeditorbuilder.py:711 rendergui.py:109 preferenceswindow.py:50
#: tools/batchrendering.py:407 tools/gmic.py:427 dialogutils.py:44
#: dialogutils.py:65 dialogutils.py:78 dialogutils.py:93
msgid "OK"
msgstr ""
#: dialogs.py:66 dialogs.py:136
msgid "Project profile:"
msgstr ""
#: dialogs.py:74 projectinfogui.py:51 projectinfogui.py:77
msgid "Profile"
msgstr ""
#: dialogs.py:80
msgid "Tracks"
msgstr ""
#: dialogs.py:97
msgid "Copying user data to XDG folders"
msgstr ""
#: dialogs.py:98
msgid "This can take up to a few minutes, please wait..."
msgstr ""
#: dialogs.py:123 dialogs.py:181
msgid "Change Project Profile"
msgstr ""
#: dialogs.py:126 dialogs.py:184
msgid "Save With Changed Profile"
msgstr ""
#: dialogs.py:128 dialogs.py:186
msgid ""
"Project Profile can only changed by saving a version\n"
"with different profile."
msgstr ""
#: dialogs.py:144
msgid "New Profile"
msgstr ""
#: dialogs.py:146 dialogs.py:201 rendergui.py:672 tools/gmic.py:761
#: tools/toolsencoding.py:40
msgid "Select Folder"
msgstr ""
#: dialogs.py:150 dialogs.py:205 rendergui.py:676 tools/toolsencoding.py:44
msgid "Folder:"
msgstr ""
#: dialogs.py:159 dialogs.py:214
msgid "Project Name:"
msgstr ""
#: dialogs.py:163 dialogs.py:218
msgid "New Project File"
msgstr ""
#: dialogs.py:192 dialogs.py:1699 tools/batchrendering.py:1320
msgid "File:"
msgstr ""
#: dialogs.py:193
msgid "File Best Match Profile:"
msgstr ""
#: dialogs.py:194
msgid "Project Current Profile:"
msgstr ""
#: dialogs.py:220
msgid "Project will be saved with profile: "
msgstr ""
#: dialogs.py:234
msgid "Save Project Backup Snapshot"
msgstr ""
#: dialogs.py:239
msgid "Select Snapshot Project Folder"
msgstr ""
#: dialogs.py:243
msgid "Snapshot Folder:"
msgstr ""
#: dialogs.py:251
msgid "Project File Name:"
msgstr ""
#: dialogs.py:271
msgid "Save Sequence Audio As Ardour Session"
msgstr ""
#: dialogs.py:276
msgid "Select Ardour Session Folder"
msgstr ""
#: dialogs.py:280
msgid "Select Ardour Session Folder:"
msgstr ""
#: dialogs.py:303
msgid "Select Project File"
msgstr ""
#: dialogs.py:312
msgid "Flowblade Projects"
msgstr ""
#: dialogs.py:322
msgid "Save Project As"
msgstr ""
#: dialogs.py:325 dialogs.py:350 dialogs.py:437 dialogs.py:460 dialogs.py:482
#: rendergui.py:123 tools/batchrendering.py:990 tools/gmic.py:403
msgid "Save"
msgstr ""
#: dialogs.py:343
msgid "Save Effect Values Data"
msgstr ""
#: dialogs.py:345
msgid "Save Compositor Values Data"
msgstr ""
#: dialogs.py:357 dialogs.py:378
msgid "Effect/Compositor Values Data"
msgstr ""
#: dialogs.py:367
msgid "Load Effect Values Data"
msgstr ""
#: dialogs.py:369
msgid "Load Compositor Values Data"
msgstr ""
#: dialogs.py:385
msgid "Export Project as XML to"
msgstr ""
#: dialogs.py:406 dialogs.py:1110 patternproducer.py:355 patternproducer.py:388
#: patternproducer.py:419
msgid "Create"
msgstr ""
#: dialogs.py:413 patternproducer.py:366
msgid "Clip Name:"
msgstr ""
#: dialogs.py:434
msgid "Save Runtime Environment Data"
msgstr ""
#: dialogs.py:446
msgid "Can't make home folder render clips folder"
msgstr ""
#: dialogs.py:447
msgid "Please create and select some other folder then '"
msgstr ""
#: dialogs.py:448
msgid "' as render clips folder"
msgstr ""
#: dialogs.py:452 dialogs.py:473
msgid "Save project '"
msgstr ""
#: dialogs.py:452
msgid "' before exiting?"
msgstr ""
#: dialogs.py:458 dialogs.py:480
msgid "Don't Save"
msgstr ""
#: dialogs.py:473
msgid "' before closing project?"
msgstr ""
#: dialogs.py:500
msgid "Project page:"
msgstr ""
#: dialogs.py:521
msgid "Upstream:"
msgstr ""
#: dialogs.py:524
msgid "Tools:"
msgstr ""
#: dialogs.py:550
msgid "Lead Developer:"
msgstr ""
#: dialogs.py:553
msgid "Developers:"
msgstr ""
#: dialogs.py:560
msgid "Contributors:"
msgstr ""
#: dialogs.py:590
msgid "Translations by:"
msgstr ""
#: dialogs.py:604
msgid "Application"
msgstr ""
#: dialogs.py:605
msgid "Thanks"
msgstr ""
#: dialogs.py:606
msgid "License"
msgstr ""
#: dialogs.py:607
msgid "Developers"
msgstr ""
#: dialogs.py:608
msgid "Translations"
msgstr ""
#: dialogs.py:623
msgid "MLT version: "
msgstr ""
#: dialogs.py:629
msgid "GTK version: "
msgstr ""
#: dialogs.py:631
msgid "Locale: "
msgstr ""
#: dialogs.py:634
msgid "INSTALLATION"
msgstr ""
#: dialogs.py:638
msgid "DEVELOPER VERSION"
msgstr ""
#: dialogs.py:640
msgid "Running from: "
msgstr ""
#: dialogs.py:668
msgid " AVAILABLE"
msgstr ""
#: dialogs.py:670
msgid " NOT AVAILABLE, "
msgstr ""
#: dialogs.py:670
msgid " MISSING"
msgstr ""
#: dialogs.py:676
msgid " FOR FILTER "
msgstr ""
#: dialogs.py:676 dialogs.py:679
msgid " NOT FOUND"
msgstr ""
#: dialogs.py:679
msgid " FOR TRANSITION "
msgstr ""
#: dialogs.py:683 dialogs.py:1448 preferenceswindow.py:63
msgid "General"
msgstr ""
#: dialogs.py:684
msgid "MLT Filters"
msgstr ""
#: dialogs.py:685
msgid "MLT Transitions"
msgstr ""
#: dialogs.py:686
msgid "Missing MLT Services"
msgstr ""
#: dialogs.py:689
msgid "Video Codecs"
msgstr ""
#: dialogs.py:690
msgid "Audio Codecs"
msgstr ""
#: dialogs.py:691
msgid "Formats"
msgstr ""
#: dialogs.py:692
msgid "Render Options"
msgstr ""
#: dialogs.py:738 guicomponents.py:2078
msgid "File Properties"
msgstr ""
#: dialogs.py:751
msgid "Clip Properties"
msgstr ""
#: dialogs.py:772
msgid "Loading project"
msgstr ""
#: dialogs.py:806
msgid "Recreating icons"
msgstr ""
#: dialogs.py:809
msgid "Update media lengths data"
msgstr ""
#: dialogs.py:812
msgid "Comparing Audio Data..."
msgstr ""
#: dialogs.py:851
msgid "Are you sure you want to delete these media files?"
msgstr ""
#: dialogs.py:852
msgid ""
"One or more of the Media Files you are deleting from the project\n"
"either have proxy files or are proxy files.\n"
"\n"
msgstr ""
#: dialogs.py:853
msgid ""
"Deleting these files could prevent converting between\n"
"using proxy files and using original media.\n"
"\n"
msgstr ""
#: dialogs.py:863
msgid "Force Delete"
msgstr ""
#: dialogs.py:874
msgid "Open last autosave?"
msgstr ""
#: dialogs.py:875
msgid ""
"It seems that Flowblade exited abnormally last time.\n"
"\n"
msgstr ""
#: dialogs.py:876
msgid ""
"If there is another instance of Flowblade running,\n"
"this dialog has probably detected its autosave file.\n"
"\n"
msgstr ""
#: dialogs.py:877
msgid "It is NOT possible to open this autosaved version later."
msgstr ""
#: dialogs.py:885 dialogs.py:929
msgid "Continue with default 'untitled' project"
msgstr ""
#: dialogs.py:886
msgid "Open Autosaved Project"
msgstr ""
#: dialogs.py:896
msgid "Open a autosave file?"
msgstr ""
#: dialogs.py:897
msgid ""
"There are multiple autosave files from application crashes.\n"
"\n"
msgstr ""
#: dialogs.py:898
msgid ""
"If you just experienced a crash, select the last created autosave "
"file\n"
"to continue working.\n"
"\n"
msgstr ""
#: dialogs.py:899
msgid ""
"If you see this at application start without a recent crash,\n"
"you should probably delete all autosave files to stop seeing this dialog."
msgstr ""
#: dialogs.py:907
msgid "Delete all autosaves"
msgstr ""
#: dialogs.py:909
msgid "Delete all but selected autosave"
msgstr ""
#: dialogs.py:930
msgid "Open Selected Autosave"
msgstr ""
#: dialogs.py:955
msgid "Change Sequence Tracks Count"
msgstr ""
#: dialogs.py:958
msgid "Change Tracks"
msgstr ""
#: dialogs.py:962
msgid ""
"Please note:\n"
"\n"
msgstr ""
#: dialogs.py:963
msgid ""
" When reducing the number of tracks the top Video track and/or bottom Audio "
"track will be removed\n"
msgstr ""
#: dialogs.py:964 dialogs.py:1737
msgid ""
" It is recommended that you save Project before completing this operation\n"
msgstr ""
#: dialogs.py:965 dialogs.py:1738
msgid " There is no Undo for this operation\n"
msgstr ""
#: dialogs.py:966 dialogs.py:1739
msgid " Current Undo Stack will be destroyed\n"
msgstr ""
#: dialogs.py:967
msgid ""
" All Clips and Compositors on deleted Tracks will be permanently destroyed"
msgstr ""
#: dialogs.py:989
msgid "Change Clip Length"
msgstr ""
#: dialogs.py:992 dialogs.py:1202 dialogs.py:1236
msgid "Ok"
msgstr ""
#: dialogs.py:1010
msgid "Create New Sequence"
msgstr ""
#: dialogs.py:1013
msgid "Create Sequence"
msgstr ""
#: dialogs.py:1020
msgid "Sequence Name:"
msgstr ""
#: dialogs.py:1028
msgid "Open For Editing:"
msgstr ""
#: dialogs.py:1051
msgid "Rename New Media Object"
msgstr ""
#: dialogs.py:1054 dialogs.py:1082 dialogs.py:1136 guicomponents.py:2073
msgid "Rename"
msgstr ""
#: dialogs.py:1061 dialogs.py:1089
msgid "New Name:"
msgstr ""
#: dialogs.py:1079 guicomponents.py:1637 guicomponents.py:2016
msgid "Rename Clip"
msgstr ""
#: dialogs.py:1107
msgid "New Range Item Group"
msgstr ""
#: dialogs.py:1114
msgid "User Group "
msgstr ""
#: dialogs.py:1117 dialogs.py:1137
msgid "New Group Name:"
msgstr ""
#: dialogs.py:1135
msgid "Rename Range Log Item Group"
msgstr ""
#: dialogs.py:1143
msgid "Can't open non-valid media"
msgstr ""
#: dialogs.py:1144
msgid "File: "
msgstr ""
#: dialogs.py:1144
msgid ""
"\n"
"is not a valid media file."
msgstr ""
#: dialogs.py:1148 dialogs.py:1170
msgid "New Marker"
msgstr ""
#: dialogs.py:1150 dialogs.py:1172 guicomponents.py:2730
msgid "Add Marker"
msgstr ""
#: dialogs.py:1157
msgid "Name for marker at "
msgstr ""
#: dialogs.py:1174
msgid "Timeline position: "
msgstr ""
#: dialogs.py:1181
msgid "Name for clip marker at "
msgstr ""
#: dialogs.py:1200
msgid "Alpha Filters Info"
msgstr ""
#: dialogs.py:1204
msgid "You are adding Alpha Filter '"
msgstr ""
#: dialogs.py:1204
msgid ""
"' into a clip. Here is some info on how Alpha Filters work on "
"Flowblade:"
msgstr ""
#: dialogs.py:1208
msgid " Alpha Filters work by modifying image's alpha channel.\n"
msgstr ""
#: dialogs.py:1209
msgid ""
" To see the effect of Alpha Filter you need composite this clip on "
"track below by adding a Compositor like 'Dissolve' into this clip.\n"
msgstr ""
#: dialogs.py:1210
msgid " Alpha Filters on clips on Track V1 have no effect."
msgstr ""
#: dialogs.py:1215
msgid "Don't show this message again."
msgstr ""
#: dialogs.py:1237
msgid "Add Image Sequence Clip"
msgstr ""
#: dialogs.py:1243
msgid "Select First Frame"
msgstr ""
#: dialogs.py:1253
msgid "First frame:"
msgstr ""
#: dialogs.py:1257
msgid "Frames per Source Image:"
msgstr ""
#: dialogs.py:1272
msgid "Export EDL"
msgstr ""
#: dialogs.py:1286
msgid "Add Transition"
msgstr ""
#: dialogs.py:1289 dialogs.py:1345 dialogs.py:1360
msgid "Apply"
msgstr ""
#: dialogs.py:1300
msgid "Rerender Transition"
msgstr ""
#: dialogs.py:1303 dialogs.py:1317 guicomponents.py:1557
msgid "Rerender"
msgstr ""
#: dialogs.py:1314
msgid "Rerender Fade"
msgstr ""
#: dialogs.py:1328
msgid "Rerender All Transitions and Fades"
msgstr ""
#: dialogs.py:1331
msgid "Rerender All"
msgstr ""
#: dialogs.py:1342 guicomponents.py:1805
msgid "Add Fade"
msgstr ""
#: dialogs.py:1362
msgid "Shortcuts Presets:"
msgstr ""
#: dialogs.py:1371
msgid "Diffence to 'Flowblade Default' Presets:"
msgstr ""
#: dialogs.py:1435
msgid "Control + N"
msgstr ""
#: dialogs.py:1435
msgid "Create New Project"
msgstr ""
#: dialogs.py:1436
msgid "Control + S"
msgstr ""
#: dialogs.py:1436
msgid "Save Project"
msgstr ""
#: dialogs.py:1438
msgid "ESCAPE"
msgstr ""
#: dialogs.py:1438
msgid "Stop Rendering Audio Levels"
msgstr ""
#: dialogs.py:1439
msgid "Control + Q"
msgstr ""
#: dialogs.py:1439
msgid "Quit"
msgstr ""
#: dialogs.py:1440
msgid "Control + Z"
msgstr ""
#: dialogs.py:1440
msgid "Undo"
msgstr ""
#: dialogs.py:1441
msgid "Control + Y"
msgstr ""
#: dialogs.py:1441
msgid "Redo"
msgstr ""
#: dialogs.py:1442
msgid "Control + O"
msgstr ""
#: dialogs.py:1442
msgid "Open Project"
msgstr ""
#: dialogs.py:1445
msgid "Control + L"
msgstr ""
#: dialogs.py:1453
msgid "Alt + I"
msgstr ""
#: dialogs.py:1453
msgid "Go To Mark In"
msgstr ""
#: dialogs.py:1454
msgid "Alt + O"
msgstr ""
#: dialogs.py:1454
msgid "Go To Mark Out"
msgstr ""
#: dialogs.py:1457
msgid "DELETE"
msgstr ""
#: dialogs.py:1458
msgid "Control + DELETE"
msgstr ""
#: dialogs.py:1465 dialogs.py:1509
msgid "Control + C"
msgstr ""
#: dialogs.py:1465
msgid "Copy Clips"
msgstr ""
#: dialogs.py:1466 dialogs.py:1510
msgid "Control + V"
msgstr ""
#: dialogs.py:1466
msgid "Paste Clips"
msgstr ""
#: dialogs.py:1470 dialogs.py:1514 dialogs.py:1529
msgid "Left Arrow "
msgstr ""
#: dialogs.py:1470
msgid "Prev Frame Trim Edit"
msgstr ""
#: dialogs.py:1471 dialogs.py:1515 dialogs.py:1530 shortcuts.py:235
msgid "Right Arrow"
msgstr ""
#: dialogs.py:1471
msgid "Next Frame Trim Edit"
msgstr ""
#: dialogs.py:1472 dialogs.py:1492
msgid "Control + Left Arrow "
msgstr ""
#: dialogs.py:1472
msgid "Back 10 Frames Trim Edit"
msgstr ""
#: dialogs.py:1473 dialogs.py:1493
msgid "Control + Right Arrow"
msgstr ""
#: dialogs.py:1473
msgid "Forward 10 Frames Trim Edit"
msgstr ""
#: dialogs.py:1482
msgid "Mouse Double Click"
msgstr ""
#: dialogs.py:1482
msgid "Toggle Track Height"
msgstr ""
#: dialogs.py:1483
msgid "Track Head Column"
msgstr ""
#: dialogs.py:1492
msgid "Move Back 10 Frames"
msgstr ""
#: dialogs.py:1493
msgid "Move Forward 10 Frames"
msgstr ""
#: dialogs.py:1498
msgid "Shift + I"
msgstr ""
#: dialogs.py:1499
msgid "Shift + O"
msgstr ""
#: dialogs.py:1500 preferenceswindow.py:65
msgid "Playback"
msgstr ""
#: dialogs.py:1505
msgid "Keypad 1-9"
msgstr ""
#: dialogs.py:1505
msgid "Same as 1-9"
msgstr ""
#: dialogs.py:1509
msgid "Copy Keyframe Value"
msgstr ""
#: dialogs.py:1510
msgid "Paste Keyframe Value"
msgstr ""
#: dialogs.py:1511
msgid "Keyframe and Geometry Editor"
msgstr ""
#: dialogs.py:1514
msgid "Move Source Video Left 1px"
msgstr ""
#: dialogs.py:1515
msgid "Move Source Video Right 1px"
msgstr ""
#: dialogs.py:1516 shortcuts.py:233
msgid "Up Arrow"
msgstr ""
#: dialogs.py:1516
msgid "Move Source Video Up 1px"
msgstr ""
#: dialogs.py:1517 shortcuts.py:232
msgid "Down Arrow"
msgstr ""
#: dialogs.py:1517
msgid "Move Source Video Down 1px"
msgstr ""
#: dialogs.py:1518
msgid "Control + Arrow"
msgstr ""
#: dialogs.py:1518
msgid "Move Source Video 10px"
msgstr ""
#: dialogs.py:1519
msgid "Control + Mouse Drag"
msgstr ""
#: dialogs.py:1519
msgid "Keep Aspect Ratio in Affine Blend scaling"
msgstr ""
#: dialogs.py:1520
msgid "Shift + Left Arrow "
msgstr ""
#: dialogs.py:1520
msgid "Scale Down"
msgstr ""
#: dialogs.py:1521
msgid "Shift + Right Arrow"
msgstr ""
#: dialogs.py:1521
msgid "Scale Up"
msgstr ""
#: dialogs.py:1522
msgid "Shift + Control + Left Arrow "
msgstr ""
#: dialogs.py:1522
msgid "Scale Down More"
msgstr ""
#: dialogs.py:1523
msgid "Shift + Control + Right Arrow"
msgstr ""
#: dialogs.py:1523
msgid "Scale Up More"
msgstr ""
#: dialogs.py:1524 shortcuts.py:282
msgid "Shift"
msgstr ""
#: dialogs.py:1524
msgid "Snap to X or Y of drag start point"
msgstr ""
#: dialogs.py:1525
msgid "Geometry Editor"
msgstr ""
#: dialogs.py:1528 guicomponents.py:1594 guicomponents.py:1670
#: guicomponents.py:1913 guicomponents.py:2074 guicomponents.py:2111
#: panels.py:102 tools/titler.py:213 tools/batchrendering.py:1068
#: shortcuts.py:244
msgid "Delete"
msgstr ""
#: dialogs.py:1528
msgid "Deletes Selected Handle"
msgstr ""
#: dialogs.py:1529 keyframeeditor.py:742
msgid "Previous Frame"
msgstr ""
#: dialogs.py:1530 keyframeeditor.py:743 shortcuts.py:188
msgid "Next Frame"
msgstr ""
#: dialogs.py:1531
msgid "RotoMask Editor"
msgstr ""
#: dialogs.py:1567
msgid "Sequence Watermark"
msgstr ""
#: dialogs.py:1569 tools/titler.py:525 proxyediting.py:423 medialinker.py:157
#: tools/gmic.py:847 tools/gmic.py:938 diskcachemanagement.py:151
msgid "Close"
msgstr ""
#: dialogs.py:1571
msgid "Sequence:"
msgstr ""
#: dialogs.py:1575
msgid "Watermark:"
msgstr ""
#: dialogs.py:1577
msgid "Set Watermark File"
msgstr ""
#: dialogs.py:1578
msgid "Remove Watermark"
msgstr ""
#: dialogs.py:1580 menuactions.py:149
msgid "Not Set"
msgstr ""
#: dialogs.py:1614
msgid "Select Watermark File"
msgstr ""
#: dialogs.py:1643
msgid "All files"
msgstr ""
#: dialogs.py:1665
msgid "Saving project snapshot"
msgstr ""
#: dialogs.py:1687
msgid "Loaded Media Profile Mismatch"
msgstr ""
#: dialogs.py:1689
msgid "Keep Current Profile"
msgstr ""
#: dialogs.py:1690
msgid "Change To File Profile"
msgstr ""
#: dialogs.py:1692
msgid "A video file was loaded that does not match the Project Profile!"
msgstr ""
#: dialogs.py:1700
msgid "File Profile:"
msgstr ""
#: dialogs.py:1701
msgid "Project Profile:"
msgstr ""
#: dialogs.py:1702
msgid ""
"Using a matching profile is recommended.\n"
"\n"
"This message is only displayed on first media load for Project."
msgstr ""
#: dialogs.py:1726
msgid "Cannot import sequence!"
msgstr ""
#: dialogs.py:1727
msgid "There are no other sequences in the Project."
msgstr ""
#: dialogs.py:1731
msgid "Import Sequence"
msgstr ""
#: dialogs.py:1734
msgid "Import"
msgstr ""
#: dialogs.py:1736
msgid "Please note:\n"
msgstr ""
#: dialogs.py:1745
msgid "Append Sequence"
msgstr ""
#: dialogs.py:1746
msgid "Insert Sequence at Playhead position"
msgstr ""
#: dialogs.py:1759
msgid "Action:"
msgstr ""
#: dialogs.py:1762
msgid "Import:"
msgstr ""
#: dialogs.py:1779
msgid "Compositors Auto Fades"
msgstr ""
#: dialogs.py:1782
msgid "Set Group Defaults"
msgstr ""
#: dialogs.py:1787
msgid "Dissolve, Blend"
msgstr ""
#: dialogs.py:1788
msgid "Affine Blend, Picture-In-Picture, Region"
msgstr ""
#: dialogs.py:1793
msgid "Compositor Auto Fades Group"
msgstr ""
#: dialogs.py:1796 dialogs.py:1807 panels.py:291 panels.py:349 panels.py:386
#: panels.py:483 tools/gmic.py:746
msgid "Length:"
msgstr ""
#: dialogs.py:1797
msgid "Add Fade In on Creation"
msgstr ""
#: dialogs.py:1808
msgid "Add Fade Out on Creation"
msgstr ""
#: dialogs.py:1825
msgid "Group Auto Fades"
msgstr ""
#: dialogs.py:1894
msgid "Timeline Audio Sync"
msgstr ""
#: dialogs.py:1897
msgid "Do Audio Sync Move Edit"
msgstr ""
#: dialogs.py:1899
msgid "Audio Sync Offset between clips media is "
msgstr ""
#: dialogs.py:1899 dialogs.py:1901 dialogs.py:1904
msgid " frames."
msgstr ""
#: dialogs.py:1901
msgid "Timeline Media Offset between clips is "
msgstr ""
#: dialogs.py:1904
msgid "To audio sync clips you need move action origin clip by "
msgstr ""
#: dialogs.py:1924
msgid "Can't put an audio clip on a video track."
msgstr ""
#: dialogs.py:1925 movemodes.py:675 dialogutils.py:218
msgid "Track "
msgstr ""
#: dialogs.py:1925
msgid " is a video track and can't display audio only material."
msgstr ""
#: dialogs.py:1930
msgid "Confirm Compositing Mode Change"
msgstr ""
#: dialogs.py:1933
msgid "Change Compositing Mode"
msgstr ""
#: dialogs.py:1935
msgid "Changing Compositing Mode destroys current Compositors and undo stack"
msgstr ""
#: dialogs.py:1936
msgid "This operation cannot be undone. Are you sure you wish to proceed?"
msgstr ""
#: editorpersistance.py:193
msgid "Empty"
msgstr ""
#: guicomponents.py:385
msgid "active"
msgstr ""
#: guicomponents.py:671
msgid "default"
msgstr ""
#: guicomponents.py:686
msgid "Autosave created "
msgstr ""
#: guicomponents.py:686
msgid " ago."
msgstr ""
#: guicomponents.py:705
msgid "Clip:"
msgstr ""
#: guicomponents.py:715
msgid "Track:"
msgstr ""
#: guicomponents.py:736
msgid "Clip: "
msgstr ""
#: guicomponents.py:738
msgid "Track: "
msgstr ""
#: guicomponents.py:819
msgid "Source:"
msgstr ""
#: guicomponents.py:820
msgid "Destination:"
msgstr ""
#: guicomponents.py:821
msgid "Length:"
msgstr ""
#: guicomponents.py:902
msgid "Items:"
msgstr ""
#: guicomponents.py:1100
msgid "Right Click to Add Media."
msgstr ""
#: guicomponents.py:1396 guicomponents.py:1400
msgid "Lock Track"
msgstr ""
#: guicomponents.py:1397 guicomponents.py:1401
msgid "Unlock Track"
msgstr ""
#: guicomponents.py:1406
msgid "Large Height"
msgstr ""
#: guicomponents.py:1411
msgid "Normal Height"
msgstr ""
#: guicomponents.py:1437 guicomponents.py:1559 guicomponents.py:1605
msgid "Open in Filters Editor"
msgstr ""
#: guicomponents.py:1444 guicomponents.py:1607 guicomponents.py:2076
msgid "Open in Clip Monitor"
msgstr ""
#: guicomponents.py:1457 middlebar.py:162
msgid "Split Audio"
msgstr ""
#: guicomponents.py:1465
msgid "Split Audio Synched"
msgstr ""
#: guicomponents.py:1472 guicomponents.py:1621
msgid "Display Audio Level"
msgstr ""
#: guicomponents.py:1475 guicomponents.py:1624
msgid "Clear Waveform"
msgstr ""
#: guicomponents.py:1478
msgid "Select Clip to Audio Sync With..."
msgstr ""
#: guicomponents.py:1491 guicomponents.py:1614
msgid "Clear Sync Relation"
msgstr ""
#: guicomponents.py:1493 guicomponents.py:1616
msgid "Select Sync Parent Clip..."
msgstr ""
#: guicomponents.py:1520 guicomponents.py:2843
msgid "Delete Compositor"
msgstr ""
#: guicomponents.py:1522
msgid "Delete Compositor/s"
msgstr ""
#: guicomponents.py:1537 guicomponents.py:1640
msgid "Clip Info"
msgstr ""
#: guicomponents.py:1591
msgid "Strech Prev Clip to Cover"
msgstr ""
#: guicomponents.py:1592
msgid "Strech Next Clip to Cover"
msgstr ""
#: guicomponents.py:1656
msgid "Open In Compositor Editor"
msgstr ""
#: guicomponents.py:1658
msgid "Sync with Origin Clip"
msgstr ""
#: guicomponents.py:1661
msgid "Obey Auto Follow"
msgstr ""
#: guicomponents.py:1674 guicomponents.py:1695
msgid "Add Filter"
msgstr ""
#: guicomponents.py:1719
msgid "Add Compositor"
msgstr ""
#: guicomponents.py:1755
msgid "Blenders"
msgstr ""
#: guicomponents.py:1773 translations.py:135 translations.py:692
msgid "Alpha"
msgstr ""
#: guicomponents.py:1789 translations.py:273 mlttransitions.py:201
msgid "Wipe"
msgstr ""
#: guicomponents.py:1825
msgid "Show Match Frame"
msgstr ""
#: guicomponents.py:1829
msgid "First Frame in Monitor"
msgstr ""
#: guicomponents.py:1834
msgid "Last Frame in Monitor"
msgstr ""
#: guicomponents.py:1841
msgid "First Frame on Timeline"
msgstr ""
#: guicomponents.py:1846
msgid "Last Frame on Timeline"
msgstr ""
#: guicomponents.py:1853 guicomponents.py:2940
msgid "Clear Match Frame"
msgstr ""
#: guicomponents.py:1863
msgid "Select"
msgstr ""
#: guicomponents.py:1867
msgid "All Clips After"
msgstr ""
#: guicomponents.py:1872
msgid "All Clips Before"
msgstr ""
#: guicomponents.py:1882
msgid "Export To Tool"
msgstr ""
#: guicomponents.py:1899 panels.py:103
msgid "Edit"
msgstr ""
#: guicomponents.py:1904 kftoolmode.py:409
msgid "Volume Keyframes"
msgstr ""
#: guicomponents.py:1908 kftoolmode.py:410
msgid "Brightness Keyframes"
msgstr ""
#: guicomponents.py:1921
msgid "Set Clip Length..."
msgstr ""
#: guicomponents.py:1924
msgid "Stretch Over Next Blank"
msgstr ""
#: guicomponents.py:1927
msgid "Stretch Over Prev Blank"
msgstr ""
#: guicomponents.py:1934
msgid "Clone Filters"
msgstr ""
#: guicomponents.py:1938
msgid "From Next Clip"
msgstr ""
#: guicomponents.py:1943
msgid "From Previous Clip"
msgstr ""
#: guicomponents.py:1952 guicomponents.py:1972
msgid "Mute"
msgstr ""
#: guicomponents.py:1956 guicomponents.py:1976
msgid "Unmute"
msgstr ""
#: guicomponents.py:1962 guicomponents.py:1993
msgid "Mute Audio"
msgstr ""
#: guicomponents.py:1987
msgid "Mute Video"
msgstr ""
#: guicomponents.py:2004
msgid "Mute All"
msgstr ""
#: guicomponents.py:2014
msgid "Properties"
msgstr ""
#: guicomponents.py:2024
msgid "Clip Color"
msgstr ""
#: guicomponents.py:2026
msgid "Default"
msgstr ""
#: guicomponents.py:2027 translations.py:526 translations.py:648
msgid "Red"
msgstr ""
#: guicomponents.py:2028 translations.py:527 translations.py:643
#: translations.py:649
msgid "Green"
msgstr ""
#: guicomponents.py:2029 translations.py:528 translations.py:644
#: translations.py:650
msgid "Blue"
msgstr ""
#: guicomponents.py:2030
msgid "Orange"
msgstr ""
#: guicomponents.py:2031
msgid "Brown"
msgstr ""
#: guicomponents.py:2032
msgid "Olive"
msgstr ""
#: guicomponents.py:2038
msgid "Markers"
msgstr ""
#: guicomponents.py:2051
msgid "No Clip Markers"
msgstr ""
#: guicomponents.py:2055
msgid "Add Clip Marker At Playhead Position"
msgstr ""
#: guicomponents.py:2056
msgid "Delete Clip Marker At Playhead Position"
msgstr ""
#: guicomponents.py:2058
msgid "Delete All Clip Markers"
msgstr ""
#: guicomponents.py:2083 guicomponents.py:2109
msgid "Render Slow/Fast Motion File"
msgstr ""
#: guicomponents.py:2085
msgid "Render Reverse Motion File"
msgstr ""
#: guicomponents.py:2087
msgid "Render Proxy File"
msgstr ""
#: guicomponents.py:2096
msgid "Toggle Active"
msgstr ""
#: guicomponents.py:2097
msgid "Reset Values"
msgstr ""
#: guicomponents.py:2099
msgid "Move Up"
msgstr ""
#: guicomponents.py:2100
msgid "Move Down"
msgstr ""
#: guicomponents.py:2108
msgid "Display In Clip Monitor"
msgstr ""
#: guicomponents.py:2110
msgid "Toggle Star"
msgstr ""
#: guicomponents.py:2118 medialinker.py:137
msgid "Set File Relink Path"
msgstr ""
#: guicomponents.py:2119 medialinker.py:139
msgid "Delete File Relink Path"
msgstr ""
#: guicomponents.py:2121
msgid "Show Full Paths"
msgstr ""
#: guicomponents.py:2187 propertyeditorbuilder.py:739
#: propertyeditorbuilder.py:794 tools/toolsencoding.py:343
msgid "Progressive"
msgstr ""
#: guicomponents.py:2189 tools/toolsencoding.py:345
msgid "Interlaced"
msgstr ""
#: guicomponents.py:2194 tools/toolsencoding.py:348
msgid "Fps: "
msgstr ""
#: guicomponents.py:2197 tools/toolsencoding.py:351
msgid "Pixel Aspect: "
msgstr ""
#: guicomponents.py:2204
msgid "Description:"
msgstr ""
#: guicomponents.py:2206
msgid "Dimensions:"
msgstr ""
#: guicomponents.py:2208
msgid "Frames per second:"
msgstr ""
#: guicomponents.py:2210 tools/titler.py:337
msgid "Size:"
msgstr ""
#: guicomponents.py:2212
msgid "Pixel aspect ratio: "
msgstr ""
#: guicomponents.py:2214 profilesmanager.py:139
msgid "Progressive:"
msgstr ""
#: guicomponents.py:2498
msgid "Current Sequence / Clip name and length"
msgstr ""
#: guicomponents.py:2583
msgid "Video:"
msgstr ""
#: guicomponents.py:2588
msgid "Audio:"
msgstr ""
#: guicomponents.py:2593
msgid "Number of Tracks:"
msgstr ""
#: guicomponents.py:2636
msgid "Frames:"
msgstr ""
#: guicomponents.py:2727
msgid "No Markers"
msgstr ""
#: guicomponents.py:2731
msgid "Delete Marker"
msgstr ""
#: guicomponents.py:2733
msgid "Delete All Markers"
msgstr ""
#: guicomponents.py:2741
msgid "Maximize Tracks"
msgstr ""
#: guicomponents.py:2742
msgid "Maximize Video Tracks"
msgstr ""
#: guicomponents.py:2743
msgid "Maximize Audio Tracks"
msgstr ""
#: guicomponents.py:2745
msgid "Minimize Tracks"
msgstr ""
#: guicomponents.py:2747
msgid "Activate All Tracks"
msgstr ""
#: guicomponents.py:2748
msgid "Activate Only Current Top Active Track"
msgstr ""
#: guicomponents.py:2750
msgid "Vertical Shrink Timeline"
msgstr ""
#: guicomponents.py:2766
msgid "Display Clip Media Thumbnails"
msgstr ""
#: guicomponents.py:2775
msgid "Snapping On"
msgstr ""
#: guicomponents.py:2784
msgid "Audio scrubbing"
msgstr ""
#: guicomponents.py:2793
msgid "Display All Audio Levels"
msgstr ""
#: guicomponents.py:2796
msgid "Display Audio Levels On Request"
msgstr ""
#: guicomponents.py:2817
msgid "Save Effect Values"
msgstr ""
#: guicomponents.py:2818
msgid "Load Effect Values"
msgstr ""
#: guicomponents.py:2819
msgid "Reset Effect Values"
msgstr ""
#: guicomponents.py:2823
msgid "Delete Effect"
msgstr ""
#: guicomponents.py:2827 guicomponents.py:2847
msgid "Close Editor"
msgstr ""
#: guicomponents.py:2837
msgid "Save Compositor Values"
msgstr ""
#: guicomponents.py:2838
msgid "Load Compositor Values"
msgstr ""
#: guicomponents.py:2839
msgid "Reset Compositor Values"
msgstr ""
#: guicomponents.py:2856
msgid "Image"
msgstr ""
#: guicomponents.py:2858
msgid "Vectorscope"
msgstr ""
#: guicomponents.py:2860
msgid "RGB Parade"
msgstr ""
#: guicomponents.py:2864
msgid "Overlay Opacity"
msgstr ""
#: guicomponents.py:2869
msgid "100%"
msgstr ""
#: guicomponents.py:2874
msgid "80%"
msgstr ""
#: guicomponents.py:2879
msgid "50%"
msgstr ""
#: guicomponents.py:2884
msgid "20%"
msgstr ""
#: guicomponents.py:2889
msgid "0%"
msgstr ""
#: guicomponents.py:2909
msgid "Trim View On"
msgstr ""
#: guicomponents.py:2914
msgid "Trim View Single Side Edits Only"
msgstr ""
#: guicomponents.py:2919
msgid "Trim View Off"
msgstr ""
#: guicomponents.py:2935
msgid "Set Current Clip Frame Match Frame"
msgstr ""
#: guicomponents.py:2953
msgid "All Files"
msgstr ""
#: guicomponents.py:2957
msgid "Video Files"
msgstr ""
#: guicomponents.py:2961
msgid "Audio Files"
msgstr ""
#: guicomponents.py:2965
msgid "Graphics Files"
msgstr ""
#: guicomponents.py:2969
msgid "Image Sequences"
msgstr ""
#: guicomponents.py:2973
msgid "Pattern Producers"
msgstr ""
#: guicomponents.py:2986
msgid "2 Columns"
msgstr ""
#: guicomponents.py:2991
msgid "3 Columns"
msgstr ""
#: guicomponents.py:2996
msgid "4 Columns"
msgstr ""
#: guicomponents.py:3001
msgid "5 Columns"
msgstr ""
#: guicomponents.py:3006
msgid "6 Columns"
msgstr ""
#: guicomponents.py:3011
msgid "7 Columns"
msgstr ""
#: guicomponents.py:3164
msgid "Display Timeline / Clip on Monitor"
msgstr ""
#: movemodes.py:674
msgid "Can't do edit on a locked track"
msgstr ""
#: movemodes.py:675
msgid " is locked. Unlock track to edit it.\n"
msgstr ""
#: panels.py:61
msgid "Number of Media File columns."
msgstr ""
#: panels.py:73
msgid "Visible Media File types."
msgstr ""
#: panels.py:97
msgid "Bins"
msgstr ""
#: panels.py:101 translations.py:641 translations.py:669 translations.py:690
#: mlttransitions.py:148 tools/titler.py:212
msgid "Add"
msgstr ""
#: panels.py:104
msgid "Add new Sequence to Project"
msgstr ""
#: panels.py:105
msgid "Delete Sequence from Project"
msgstr ""
#: panels.py:106
msgid "Start editing Sequence"
msgstr ""
#: panels.py:120
msgid "Sequences"
msgstr ""
#: panels.py:120
msgid ""
"A Sequence is the full contents of the timeline creating a program, a "
"movie."
msgstr ""
#: panels.py:178 rendergui.py:686 tools/toolsencoding.py:54
msgid "Name:"
msgstr ""
#: panels.py:179
msgid "Path:"
msgstr ""
#: panels.py:180 panels.py:214
msgid "Image Size:"
msgstr ""
#: panels.py:181 tools/batchrendering.py:1039
msgid "Frames Per Second:"
msgstr ""
#: panels.py:182
msgid "Playtime:"
msgstr ""
#: panels.py:183 panels.py:216
msgid "Video Codec:"
msgstr ""
#: panels.py:184 panels.py:217
msgid "Audio Codec:"
msgstr ""
#: panels.py:185
msgid "Audio Channels:"
msgstr ""
#: panels.py:186
msgid "Audio Sample Rate:"
msgstr ""
#: panels.py:187
msgid "Best Profile:"
msgstr ""
#: panels.py:188
msgid "Matches Project Profile:"
msgstr ""
#: panels.py:211 tools/gmic.py:744
msgid "Mark In:"
msgstr ""
#: panels.py:212 tools/gmic.py:745
msgid "Mark Out:"
msgstr ""
#: panels.py:213
msgid "Clip Length:"
msgstr ""
#: panels.py:215
msgid "Media Path:"
msgstr ""
#: panels.py:245
msgid "Composite clip on:"
msgstr ""
#: panels.py:258 panels.py:470 rendergui.py:699 tools/toolsencoding.py:67
msgid "Type:"
msgstr ""
#: panels.py:267
msgid "Wipe Pattern:"
msgstr ""
#: panels.py:273
msgid "Dip Color:"
msgstr ""
#: panels.py:297
msgid "First Clip Out Handle:"
msgstr ""
#: panels.py:298 panels.py:301
msgid " frame(s)"
msgstr ""
#: panels.py:300
msgid "Second Clip In Handle:"
msgstr ""
#: panels.py:339 panels.py:513
msgid "Transition Options"
msgstr ""
#: panels.py:340 panels.py:377 panels.py:414 panels.py:457 panels.py:514
msgid "Encoding"
msgstr ""
#: panels.py:341
msgid "Media Overlap info"
msgstr ""
#: panels.py:376
msgid "Transition"
msgstr ""
#: panels.py:413 translations.py:590
msgid "Fade"
msgstr ""
#: panels.py:422
msgid "Transitions / Fades to be rerendered:"
msgstr ""
#: panels.py:427
msgid "There are "
msgstr ""
#: panels.py:427
msgid ""
" Transitions / Fades that cannot be rerendered, either because they are\n"
"created with Flowblade version <=1.14 or the source clips are no longer on "
"timeline."
msgstr ""
#: panels.py:456
msgid "Info"
msgstr ""
#: panels.py:466 mlttransitions.py:168 mlttransitions.py:203
msgid "Fade In"
msgstr ""
#: panels.py:467 mlttransitions.py:169 mlttransitions.py:204
msgid "Fade Out"
msgstr ""
#: panels.py:475
msgid "Color:"
msgstr ""
#: persistance.py:429
msgid "Building sequence "
msgstr ""
#: persistance.py:470
msgid "Loading icons"
msgstr ""
#: persistance.py:715 persistance.py:735
msgid "Relative file search for "
msgstr ""
#: projectdata.py:79
msgid "untitled"
msgstr ""
#: projectdata.py:211
msgid "bin_"
msgstr ""
#: projectdata.py:518
msgid "Created using dialog"
msgstr ""
#: projectdata.py:520
msgid "Created using Save As... "
msgstr ""
#: projectdata.py:522
msgid "Saved "
msgstr ""
#: projectdata.py:525
msgid "Saved as "
msgstr ""
#: projectdata.py:527
msgid "Rendered "
msgstr ""
#: projectdata.py:529
msgid "Saved backup snapshot"
msgstr ""
#: projectdata.py:531
msgid "Media load"
msgstr ""
#: projectdata.py:533
msgid "Saved with changed profile"
msgstr ""
#: render.py:219
msgid "Reset"
msgstr ""
#: render.py:221
msgid "To Queue"
msgstr ""
#: render.py:222
msgid "Save Project in Render Queue"
msgstr ""
#: render.py:225
msgid "Select render range"
msgstr ""
#: render.py:226
msgid "Reset all render options to defaults"
msgstr ""
#: render.py:227
msgid "Begin Rendering"
msgstr ""
#: render.py:249
msgid "Output File: "
msgstr ""
#: render.py:251
msgid "Estimated time left: "
msgstr ""
#: render.py:253
msgid "Render time: "
msgstr ""
#: render.py:270 render.py:296
msgid "Render Time: "
msgstr ""
#: render.py:276 render.py:294
msgid "Estimated Time Left: "
msgstr ""
#: render.py:298
msgid "Render Complete!"
msgstr ""
#: render.py:415 render.py:517
msgid "A File with given path exists!"
msgstr ""
#: render.py:416 render.py:518
msgid ""
"It is not allowed to render Motion Files with same paths as existing files.\n"
"Select another name for file."
msgstr ""
#: render.py:480
msgid "Rendering Motion Clip"
msgstr ""
#: render.py:481 render.py:579
msgid "Motion Clip File: "
msgstr ""
#: render.py:578
msgid "Rendering Reverse Clip"
msgstr ""
#: render.py:628
msgid "Rendering Transition Clip"
msgstr ""
#: syncsplitevent.py:136
msgid "Sync parent clips must be on track V1"
msgstr ""
#: syncsplitevent.py:137
msgid "Selected sync parent clip is on track "
msgstr ""
#: syncsplitevent.py:137
msgid ""
".\n"
"You can only sync to clips that are on track V1."
msgstr ""
#: translations.py:129 translations.py:444
msgid "Color"
msgstr ""
#: translations.py:130
msgid "Color Effect"
msgstr ""
#: translations.py:131
msgid "Audio"
msgstr ""
#: translations.py:132
msgid "Audio Filter"
msgstr ""
#: translations.py:133 translations.py:186 translations.py:409
#: translations.py:486
msgid "Blur"
msgstr ""
#: translations.py:134 translations.py:570 propertyeditorbuilder.py:754
#: propertyeditorbuilder.py:809
msgid "Distort"
msgstr ""
#: translations.py:136
msgid "Movement"
msgstr ""
#: translations.py:137 mlttransitions.py:143
msgid "Transform"
msgstr ""
#: translations.py:138 translations.py:505
msgid "Edge"
msgstr ""
#: translations.py:139
msgid "Fix"
msgstr ""
#: translations.py:140
msgid "Artistic"
msgstr ""
#: translations.py:144
msgid "Alpha Gradient"
msgstr ""
#: translations.py:145
msgid "Crop"
msgstr ""
#: translations.py:146
msgid "Alpha Shape"
msgstr ""
#: translations.py:148 translations.py:294
msgid "Volume"
msgstr ""
#: translations.py:149
msgid "Pan"
msgstr ""
#: translations.py:150
msgid "Pan Keyframed"
msgstr ""
#: translations.py:151
msgid "Mono to Stereo"
msgstr ""
#: translations.py:152
msgid "Swap Channels"
msgstr ""
#: translations.py:154
msgid "Pitchshifter"
msgstr ""
#: translations.py:155
msgid "Distort - Barry's Satan"
msgstr ""
#: translations.py:156
msgid "Frequency Shift - Bode/Moog"
msgstr ""
#: translations.py:157
msgid "Equalize - DJ 3-band"
msgstr ""
#: translations.py:158
msgid "Flanger - DJ"
msgstr ""
#: translations.py:159
msgid "Declipper"
msgstr ""
#: translations.py:160
msgid "Delayorama"
msgstr ""
#: translations.py:161
msgid "Distort - Diode Processor"
msgstr ""
#: translations.py:162
msgid "Distort - Foldover"
msgstr ""
#: translations.py:163
msgid "Highpass - Butterworth"
msgstr ""
#: translations.py:164
msgid "Lowpass - Butterworth"
msgstr ""
#: translations.py:165
msgid "GSM Simulator"
msgstr ""
#: translations.py:166
msgid "Reverb - GVerb"
msgstr ""
#: translations.py:167
msgid "Noise Gate"
msgstr ""
#: translations.py:168
msgid "Bandpass"
msgstr ""
#: translations.py:169
msgid "Pitchscaler - High Quality"
msgstr ""
#: translations.py:170
msgid "Equalize - Multiband"
msgstr ""
#: translations.py:171
msgid "Reverb - Plate"
msgstr ""
#: translations.py:172
msgid "Distort - Pointer cast"
msgstr ""
#: translations.py:173
msgid "Rate Shifter"
msgstr ""
#: translations.py:174
msgid "Signal Shifter"
msgstr ""
#: translations.py:175
msgid "Distort - Sinus Wavewrap"
msgstr ""
#: translations.py:176
msgid "Vinyl Effect"
msgstr ""
#: translations.py:177
msgid "Chorus - Multivoice"
msgstr ""
#: translations.py:179
msgid "Charcoal"
msgstr ""
#: translations.py:180
msgid "Glow"
msgstr ""
#: translations.py:181
msgid "Old Film"
msgstr ""
#: translations.py:182
msgid "Scanlines"
msgstr ""
#: translations.py:183
msgid "Cartoon"
msgstr ""
#: translations.py:185
msgid "Pixelize"
msgstr ""
#: translations.py:187
msgid "Grain"
msgstr ""
#: translations.py:189
msgid "Grayscale"
msgstr ""
#: translations.py:190 translations.py:424 translations.py:426
msgid "Contrast"
msgstr ""
#: translations.py:191 translations.py:427 mlttransitions.py:162
msgid "Saturation"
msgstr ""
#: translations.py:192 translations.py:408 translations.py:460
#: translations.py:485 translations.py:573 translations.py:577
msgid "Invert"
msgstr ""
#: translations.py:193 translations.py:428 mlttransitions.py:158
msgid "Hue"
msgstr ""
#: translations.py:194 translations.py:425 translations.py:429
#: translations.py:430
msgid "Brightness"
msgstr ""
#: translations.py:195 translations.py:656
msgid "Sepia"
msgstr ""
#: translations.py:196
msgid "Tint"
msgstr ""
#: translations.py:197
msgid "White Balance"
msgstr ""
#: translations.py:198 translations.py:510
msgid "Levels"
msgstr ""
#: translations.py:200
msgid "Color Clustering"
msgstr ""
#: translations.py:201
msgid "Chroma Hold"
msgstr ""
#: translations.py:202
msgid "Three Layer"
msgstr ""
#: translations.py:203
msgid "Threshold0r"
msgstr ""
#: translations.py:204
msgid "Technicolor"
msgstr ""
#: translations.py:205
msgid "Primaries"
msgstr ""
#: translations.py:206
msgid "Color Distance"
msgstr ""
#: translations.py:207 translations.py:446 translations.py:451
msgid "Threshold"
msgstr ""
#: translations.py:209
msgid "Waves"
msgstr ""
#: translations.py:210
msgid "Lens Correction"
msgstr ""
#: translations.py:211 translations.py:458
msgid "Flip"
msgstr ""
#: translations.py:212
msgid "Mirror"
msgstr ""
#: translations.py:213
msgid "V Sync"
msgstr ""
#: translations.py:215
msgid "Edge Glow"
msgstr ""
#: translations.py:216
msgid "Sobel"
msgstr ""
#: translations.py:218
msgid "Denoise"
msgstr ""
#: translations.py:219 translations.py:533
msgid "Sharpness"
msgstr ""
#: translations.py:220
msgid "Letterbox"
msgstr ""
#: translations.py:222
msgid "Baltan"
msgstr ""
#: translations.py:223
msgid "Vertigo"
msgstr ""
#: translations.py:224
msgid "Nervous"
msgstr ""
#: translations.py:225
msgid "Freeze"
msgstr ""
#: translations.py:227 translations.py:454
msgid "Rotate"
msgstr ""
#: translations.py:228
msgid "Shear"
msgstr ""
#: translations.py:229 translations.py:265
msgid "Translate"
msgstr ""
#: translations.py:232
msgid "Color Select"
msgstr ""
#: translations.py:233
msgid "Alpha Modify"
msgstr ""
#: translations.py:234
msgid "Spill Supress"
msgstr ""
#: translations.py:235
msgid "RGB Noise"
msgstr ""
#: translations.py:236
msgid "Box Blur"
msgstr ""
#: translations.py:237
msgid "IRR Blur"
msgstr ""
#: translations.py:238
msgid "Color Halftone"
msgstr ""
#: translations.py:239
msgid "Dither"
msgstr ""
#: translations.py:240
msgid "Vignette"
msgstr ""
#: translations.py:241
msgid "Vignette Advanced"
msgstr ""
#: translations.py:242
msgid "Emboss"
msgstr ""
#: translations.py:243
msgid "3 Point Balance"
msgstr ""
#: translations.py:244
msgid "Colorize"
msgstr ""
#: translations.py:245
msgid "Brightness Keyframed"
msgstr ""
#: translations.py:246
msgid "RGB Adjustment"
msgstr ""
#: translations.py:247
msgid "Color Tap"
msgstr ""
#: translations.py:248
msgid "Posterize"
msgstr ""
#: translations.py:249
msgid "Soft Glow"
msgstr ""
#: translations.py:250
msgid "Newspaper"
msgstr ""
#: translations.py:252
msgid "Luma Key"
msgstr ""
#: translations.py:253
msgid "Chroma Key"
msgstr ""
#: translations.py:254
msgid "Affine"
msgstr ""
#: translations.py:255
msgid "Color Adjustment"
msgstr ""
#: translations.py:256
msgid "Color Grading"
msgstr ""
#: translations.py:257
msgid "Curves"
msgstr ""
#: translations.py:258
msgid "Lift Gain Gamma"
msgstr ""
#: translations.py:259
msgid "Image Grid"
msgstr ""
#: translations.py:261
msgid "Color Lift Gain Gamma"
msgstr ""
#: translations.py:262
msgid "Color Channel Mixer"
msgstr ""
#: translations.py:263
msgid "Lens Correction AV"
msgstr ""
#: translations.py:264
msgid "Perspective"
msgstr ""
#: translations.py:266
msgid "Lut3D"
msgstr ""
#: translations.py:267
msgid "Normalize"
msgstr ""
#: translations.py:268
msgid "File Luma to Alpha"
msgstr ""
#: translations.py:269
msgid "Gradient Tint"
msgstr ""
#: translations.py:270
msgid "RotoMask"
msgstr ""
#: translations.py:271
msgid "Lens Defisher"
msgstr ""
#: translations.py:272
msgid "Position Scale"
msgstr ""
#: translations.py:278 translations.py:461
msgid "Position"
msgstr ""
#: translations.py:279
msgid "Grad width"
msgstr ""
#: translations.py:280 translations.py:292 translations.py:455
msgid "Tilt"
msgstr ""
#: translations.py:281 translations.py:640
msgid "Min"
msgstr ""
#: translations.py:282 translations.py:639
msgid "Max"
msgstr ""
#: translations.py:283 translations.py:481
msgid "Left"
msgstr ""
#: translations.py:284 translations.py:482
msgid "Right"
msgstr ""
#: translations.py:285 translations.py:483
msgid "Top"
msgstr ""
#: translations.py:286 translations.py:484
msgid "Bottom"
msgstr ""
#: translations.py:287
msgid "Shape"
msgstr ""
#: translations.py:288
msgid "Pos X"
msgstr ""
#: translations.py:289
msgid "Pos Y"
msgstr ""
#: translations.py:290
msgid "Size X"
msgstr ""
#: translations.py:291
msgid "Size Y"
msgstr ""
#: translations.py:293
msgid "Trans. Width"
msgstr ""
#: translations.py:295 translations.py:296
msgid "Left/Right"
msgstr ""
#: translations.py:297 translations.py:300 translations.py:303
#: translations.py:305 translations.py:309 translations.py:313
#: translations.py:314 translations.py:325 translations.py:327
#: translations.py:330 translations.py:333 translations.py:336
#: translations.py:339 translations.py:347 translations.py:355
#: translations.py:359 translations.py:361 translations.py:377
#: translations.py:381 translations.py:384 translations.py:386
#: translations.py:388 translations.py:390 translations.py:396
#: translations.py:403
msgid "Dry/Wet"
msgstr ""
#: translations.py:298
msgid "Pitch Shift"
msgstr ""
#: translations.py:299
msgid "Buffer Size"
msgstr ""
#: translations.py:301
msgid "Decay Time(samples)"
msgstr ""
#: translations.py:302
msgid "Knee Point(dB)"
msgstr ""
#: translations.py:304
msgid "Frequency shift"
msgstr ""
#: translations.py:306
msgid "Low Gain(dB)"
msgstr ""
#: translations.py:307
msgid "Mid Gain(dB)"
msgstr ""
#: translations.py:308
msgid "High Gain(dB)"
msgstr ""
#: translations.py:310
msgid "Oscillation period(s)"
msgstr ""
#: translations.py:311
msgid "Oscillation depth(ms)"
msgstr ""
#: translations.py:312
msgid "Feedback%"
msgstr ""
#: translations.py:315
msgid "Random seed"
msgstr ""
#: translations.py:316
msgid "Input Gain(dB)"
msgstr ""
#: translations.py:317
msgid "Feedback(%)"
msgstr ""
#: translations.py:318
msgid "Number of taps"
msgstr ""
#: translations.py:319
msgid "First Delay(s)"
msgstr ""
#: translations.py:320
msgid "Delay Range(s)"
msgstr ""
#: translations.py:321
msgid "Delay Change"
msgstr ""
#: translations.py:322
msgid "Delay Random(%)"
msgstr ""
#: translations.py:323
msgid "Amplitude Change"
msgstr ""
#: translations.py:324
msgid "Amplitude Random(%)"
msgstr ""
#: translations.py:326 translations.py:389 translations.py:435
#: translations.py:467
msgid "Amount"
msgstr ""
#: translations.py:328
msgid "Drive"
msgstr ""
#: translations.py:329
msgid "Skew"
msgstr ""
#: translations.py:331 translations.py:334
msgid "Cutoff Frequency(Hz)"
msgstr ""
#: translations.py:332 translations.py:335
msgid "Resonance"
msgstr ""
#: translations.py:337
msgid "Passes"
msgstr ""
#: translations.py:338
msgid "Error Rate"
msgstr ""
#: translations.py:340
msgid "Roomsize"
msgstr ""
#: translations.py:341
msgid "Reverb time(s)"
msgstr ""
#: translations.py:342 translations.py:379
msgid "Damping"
msgstr ""
#: translations.py:343
msgid "Input bandwith"
msgstr ""
#: translations.py:344
msgid "Dry signal level(dB)"
msgstr ""
#: translations.py:345
msgid "Early reflection level(dB)"
msgstr ""
#: translations.py:346
msgid "Tail level(dB)"
msgstr ""
#: translations.py:348
msgid "LF keyfilter(Hz)"
msgstr ""
#: translations.py:349
msgid "HF keyfilter(Hz)"
msgstr ""
#: translations.py:350
msgid "Threshold(dB)"
msgstr ""
#: translations.py:351
msgid "Attack(ms)"
msgstr ""
#: translations.py:352
msgid "Hold(ms)"
msgstr ""
#: translations.py:353
msgid "Decay(ms)"
msgstr ""
#: translations.py:354
msgid "Range(dB)"
msgstr ""
#: translations.py:356
msgid "Center Frequency(Hz)"
msgstr ""
#: translations.py:357
msgid "Bandwidth(Hz)"
msgstr ""
#: translations.py:358
msgid "Stages"
msgstr ""
#: translations.py:360
msgid "Pitch-coefficient"
msgstr ""
#: translations.py:362
msgid "50Hz gain"
msgstr ""
#: translations.py:363
msgid "100Hz gain"
msgstr ""
#: translations.py:364
msgid "156Hz gain"
msgstr ""
#: translations.py:365
msgid "220Hz gain"
msgstr ""
#: translations.py:366
msgid "311Hz gain"
msgstr ""
#: translations.py:367
msgid "440Hz gain"
msgstr ""
#: translations.py:368
msgid "622Hz gain"
msgstr ""
#: translations.py:369
msgid "880Hz gain"
msgstr ""
#: translations.py:370
msgid "1250Hz gain"
msgstr ""
#: translations.py:371
msgid "1750Hz gain"
msgstr ""
#: translations.py:372
msgid "2500Hz gain"
msgstr ""
#: translations.py:373
msgid "3500Hz gain"
msgstr ""
#: translations.py:374
msgid "5000Hz gain"
msgstr ""
#: translations.py:375
msgid "100000Hz gain"
msgstr ""
#: translations.py:376
msgid "200000Hz gain"
msgstr ""
#: translations.py:378
msgid "Reverb time"
msgstr ""
#: translations.py:380 translations.py:383
msgid "Dry/Wet mix"
msgstr ""
#: translations.py:382
msgid "Effect cutoff(Hz)"
msgstr ""
#: translations.py:385
msgid "Rate"
msgstr ""
#: translations.py:387
msgid "Sift"
msgstr ""
#: translations.py:391
msgid "Year"
msgstr ""
#: translations.py:392
msgid "RPM"
msgstr ""
#: translations.py:393
msgid "Surface warping"
msgstr ""
#: translations.py:394
msgid "Cracle"
msgstr ""
#: translations.py:395
msgid "Wear"
msgstr ""
#: translations.py:397
msgid "Number of voices"
msgstr ""
#: translations.py:398
msgid "Delay base(ms)"
msgstr ""
#: translations.py:399
msgid "Voice separation(ms)"
msgstr ""
#: translations.py:400
msgid "Detune(%)"
msgstr ""
#: translations.py:401
msgid "Oscillation frequency(Hz)"
msgstr ""
#: translations.py:402
msgid "Output attenuation(dB)"
msgstr ""
#: translations.py:404
msgid "X Scatter"
msgstr ""
#: translations.py:405
msgid "Y Scatter"
msgstr ""
#: translations.py:406
msgid "Scale"
msgstr ""
#: translations.py:407
msgid "Mix"
msgstr ""
#: translations.py:410
msgid "Delta"
msgstr ""
#: translations.py:411
msgid "Duration"
msgstr ""
#: translations.py:412
msgid "Bright. up"
msgstr ""
#: translations.py:413
msgid "Bright. down"
msgstr ""
#: translations.py:414
msgid "Bright. dur."
msgstr ""
#: translations.py:415
msgid "Develop up"
msgstr ""
#: translations.py:416
msgid "Develop down"
msgstr ""
#: translations.py:417
msgid "Develop dur."
msgstr ""
#: translations.py:418
msgid "Triplevel"
msgstr ""
#: translations.py:419
msgid "Difference Space"
msgstr ""
#: translations.py:420
msgid "Block width"
msgstr ""
#: translations.py:421
msgid "Block height"
msgstr ""
#: translations.py:422 translations.py:468
msgid "Size"
msgstr ""
#: translations.py:431
msgid "U"
msgstr ""
#: translations.py:432
msgid "V"
msgstr ""
#: translations.py:433 translations.py:440
msgid "Black"
msgstr ""
#: translations.py:434 translations.py:441
msgid "White"
msgstr ""
#: translations.py:436
msgid "Neutral Color"
msgstr ""
#: translations.py:437 translations.py:438
msgid "Input"
msgstr ""
#: translations.py:439
msgid "Gamma"
msgstr ""
#: translations.py:442
msgid "Num"
msgstr ""
#: translations.py:443
msgid "Dist. weight"
msgstr ""
#: translations.py:445
msgid "Variance"
msgstr ""
#: translations.py:447
msgid "Red Saturation"
msgstr ""
#: translations.py:448
msgid "Yellow Saturation"
msgstr ""
#: translations.py:449
msgid "Factor"
msgstr ""
#: translations.py:450
msgid "Source color"
msgstr ""
#: translations.py:452
msgid "Amplitude"
msgstr ""
#: translations.py:453
msgid "Frequency"
msgstr ""
#: translations.py:456
msgid "Center Correct"
msgstr ""
#: translations.py:457
msgid "Edges Correct"
msgstr ""
#: translations.py:459
msgid "Axis"
msgstr ""
#: translations.py:462
msgid "Edge Lightning"
msgstr ""
#: translations.py:463
msgid "Edge Brightness"
msgstr ""
#: translations.py:464
msgid "Non-Edge Brightness"
msgstr ""
#: translations.py:465
msgid "Spatial"
msgstr ""
#: translations.py:466
msgid "Temporal"
msgstr ""
#: translations.py:469
msgid "Border width"
msgstr ""
#: translations.py:470
msgid "Phase Incr."
msgstr ""
#: translations.py:471
msgid "Zoom"
msgstr ""
#: translations.py:472
msgid "Freeze Frame"
msgstr ""
#: translations.py:473
msgid "Freeze After"
msgstr ""
#: translations.py:474
msgid "Freeze Before"
msgstr ""
#: translations.py:475
msgid "Angle"
msgstr ""
#: translations.py:476 translations.py:479 translations.py:480
msgid "transition.geometry"
msgstr ""
#: translations.py:477 translations.py:568
msgid "Shear X"
msgstr ""
#: translations.py:478 translations.py:569
msgid "Shear Y"
msgstr ""
#: translations.py:487 translations.py:488 translations.py:567
#: translations.py:571
msgid "Opacity"
msgstr ""
#: translations.py:489
msgid "Rotate X"
msgstr ""
#: translations.py:490
msgid "Rotate Y"
msgstr ""
#: translations.py:491
msgid "Rotate Z"
msgstr ""
#: translations.py:493
msgid "Edge Mode"
msgstr ""
#: translations.py:494
msgid "Sel. Space"
msgstr ""
#: translations.py:495
msgid "Operation"
msgstr ""
#: translations.py:496 translations.py:632
msgid "Hard"
msgstr ""
#: translations.py:497
msgid "Selection subspace"
msgstr ""
#: translations.py:498
msgid "R/A/Hue"
msgstr ""
#: translations.py:499
msgid "G/B/Chroma"
msgstr ""
#: translations.py:500
msgid "B/I/I"
msgstr ""
#: translations.py:501
msgid "Supress"
msgstr ""
#: translations.py:502 translations.py:662
msgid "Horizontal"
msgstr ""
#: translations.py:503 translations.py:663
msgid "Vertical"
msgstr ""
#: translations.py:504
msgid "Type"
msgstr ""
#: translations.py:506
msgid "Dot Radius"
msgstr ""
#: translations.py:507
msgid "Cyan Angle"
msgstr ""
#: translations.py:508
msgid "Magenta Angle"
msgstr ""
#: translations.py:509
msgid "Yellow Angle"
msgstr ""
#: translations.py:511
msgid "Matrix Type"
msgstr ""
#: translations.py:512
msgid "Aspect"
msgstr ""
#: translations.py:513
msgid "Center Size"
msgstr ""
#: translations.py:514
msgid "Azimuth"
msgstr ""
#: translations.py:515 translations.py:520
msgid "Lightness"
msgstr ""
#: translations.py:516
msgid "Bump Height"
msgstr ""
#: translations.py:517
msgid "Gray"
msgstr ""
#: translations.py:518
msgid "Split Preview"
msgstr ""
#: translations.py:519
msgid "Source on Left"
msgstr ""
#: translations.py:521
msgid "Channel"
msgstr ""
#: translations.py:522
msgid "Input black level"
msgstr ""
#: translations.py:523
msgid "Input white level"
msgstr ""
#: translations.py:524
msgid "Black output"
msgstr ""
#: translations.py:525
msgid "White output"
msgstr ""
#: translations.py:529
msgid "Action"
msgstr ""
#: translations.py:530
msgid "Keep Luma"
msgstr ""
#: translations.py:531
msgid "Luma Formula"
msgstr ""
#: translations.py:532
msgid "Effect"
msgstr ""
#: translations.py:534
msgid "Blend Type"
msgstr ""
#: translations.py:536
msgid "Key Color"
msgstr ""
#: translations.py:537
msgid "Pre-Level"
msgstr ""
#: translations.py:538
msgid "Post-Level"
msgstr ""
#: translations.py:539
msgid "Slope"
msgstr ""
#: translations.py:540
msgid "Luma Band"
msgstr ""
#: translations.py:542
msgid "Gain"
msgstr ""
#: translations.py:543
msgid "Input White Level"
msgstr ""
#: translations.py:544
msgid "Input Black Level"
msgstr ""
#: translations.py:545
msgid "Black Output"
msgstr ""
#: translations.py:546
msgid "White Output"
msgstr ""
#: translations.py:547
msgid "Rows"
msgstr ""
#: translations.py:548
msgid "Columns"
msgstr ""
#: translations.py:549
msgid "Color Temperature"
msgstr ""
#: translations.py:550 translations.py:565
msgid "Select .cube file"
msgstr ""
#: translations.py:551
msgid "Red Ch. Red Gain"
msgstr ""
#: translations.py:552
msgid "Red Ch. Green Gain"
msgstr ""
#: translations.py:553
msgid "Red Ch. Blue Gain"
msgstr ""
#: translations.py:554
msgid "Green Ch. Red Gain"
msgstr ""
#: translations.py:555
msgid "Green Ch. Green Gain"
msgstr ""
#: translations.py:556
msgid "Green Ch. Blue Gain"
msgstr ""
#: translations.py:557
msgid "Blue Ch. Red Gain"
msgstr ""
#: translations.py:558
msgid "Blue Ch. Green Gain"
msgstr ""
#: translations.py:559
msgid "Blue Ch. Blue Gain"
msgstr ""
#: translations.py:560
msgid "Center X"
msgstr ""
#: translations.py:561
msgid "Center Y"
msgstr ""
#: translations.py:562
msgid "Quad Distortion"
msgstr ""
#: translations.py:563
msgid "Double Quad Distortion"
msgstr ""
#: translations.py:564
msgid "Level"
msgstr ""
#: translations.py:572 translations.py:576
msgid "Wipe Type"
msgstr ""
#: translations.py:574 translations.py:578
msgid "Softness"
msgstr ""
#: translations.py:575
msgid "Wipe Amount"
msgstr ""
#: translations.py:579
msgid "Fade Out Length"
msgstr ""
#: translations.py:580
msgid "Fade In Length"
msgstr ""
#: translations.py:581
msgid "Wipe Direction"
msgstr ""
#: translations.py:582 translations.py:583
msgid "Blend Mode"
msgstr ""
#: translations.py:584
msgid "Analysis Length"
msgstr ""
#: translations.py:585
msgid "Max Gain"
msgstr ""
#: translations.py:586
msgid "Min Gain"
msgstr ""
#: translations.py:587
msgid "Select file"
msgstr ""
#: translations.py:588
msgid "Smooth"
msgstr ""
#: translations.py:589
msgid "Radius"
msgstr ""
#: translations.py:591
msgid "Start Opacity"
msgstr ""
#: translations.py:592
msgid "End Opacity"
msgstr ""
#: translations.py:593
msgid "End Color"
msgstr ""
#: translations.py:594
msgid "Start Color"
msgstr ""
#: translations.py:595
msgid "Start X"
msgstr ""
#: translations.py:596
msgid "Start Y"
msgstr ""
#: translations.py:597
msgid "End Y"
msgstr ""
#: translations.py:598
msgid "End X"
msgstr ""
#: translations.py:599
msgid "Gradient Type"
msgstr ""
#: translations.py:600
msgid "Radial Offset"
msgstr ""
#: translations.py:601
msgid "Feather Passes"
msgstr ""
#: translations.py:602
msgid "Alpha Mode"
msgstr ""
#: translations.py:603
msgid "Feather"
msgstr ""
#: translations.py:604
msgid "Mode"
msgstr ""
#: translations.py:605
msgid "Input Pixel Aspect Ratio"
msgstr ""
#: translations.py:606
msgid "Direction"
msgstr ""
#: translations.py:607
msgid "Lens Projection"
msgstr ""
#: translations.py:608
msgid "Interpolator"
msgstr ""
#: translations.py:609
msgid "Strength"
msgstr ""
#: translations.py:610
msgid "X Scale"
msgstr ""
#: translations.py:611
msgid "Y Scale"
msgstr ""
#: translations.py:612
msgid "Rotation"
msgstr ""
#: translations.py:613
msgid "X Position"
msgstr ""
#: translations.py:614
msgid "Y Position"
msgstr ""
#: translations.py:618 translations.py:624
msgid "Shave"
msgstr ""
#: translations.py:619
msgid "Rectangle"
msgstr ""
#: translations.py:620
msgid "Ellipse"
msgstr ""
#: translations.py:621
msgid "Triangle"
msgstr ""
#: translations.py:622 shortcuts.py:212 workflow.py:71
msgid "Box"
msgstr ""
#: translations.py:623 translations.py:637
msgid "Diamond"
msgstr ""
#: translations.py:625
msgid "Shrink Hard"
msgstr ""
#: translations.py:626
msgid "Shrink Soft"
msgstr ""
#: translations.py:627
msgid "Grow Hard"
msgstr ""
#: translations.py:628
msgid "Grow Soft"
msgstr ""
#: translations.py:629
msgid "RGB"
msgstr ""
#: translations.py:630
msgid "ABI"
msgstr ""
#: translations.py:631
msgid "HCI"
msgstr ""
#: translations.py:633
msgid "Fat"
msgstr ""
#: translations.py:634
msgid "Normal"
msgstr ""
#: translations.py:635
msgid "Skinny"
msgstr ""
#: translations.py:636
msgid "Ellipsoid"
msgstr ""
#: translations.py:638 shortcuts.py:207
msgid "Overwrite"
msgstr ""
#: translations.py:642 translations.py:691 mlttransitions.py:165
msgid "Subtract"
msgstr ""
#: translations.py:645
msgid "Sharper"
msgstr ""
#: translations.py:646
msgid "Fuzzier"
msgstr ""
#: translations.py:647
msgid "Luma"
msgstr ""
#: translations.py:651
msgid "Add Constant"
msgstr ""
#: translations.py:652
msgid "Change Gamma"
msgstr ""
#: translations.py:653 translations.py:671 mlttransitions.py:160
msgid "Multiply"
msgstr ""
#: translations.py:654
msgid "XPro"
msgstr ""
#: translations.py:655
msgid "OldPhoto"
msgstr ""
#: translations.py:657
msgid "Heat"
msgstr ""
#: translations.py:658
msgid "XRay"
msgstr ""
#: translations.py:659
msgid "RedGreen"
msgstr ""
#: translations.py:660
msgid "YellowBlue"
msgstr ""
#: translations.py:661
msgid "Esses"
msgstr ""
#: translations.py:664
msgid "Shadows"
msgstr ""
#: translations.py:665
msgid "Midtones"
msgstr ""
#: translations.py:666
msgid "Highlights"
msgstr ""
#: translations.py:667
msgid "Forward"
msgstr ""
#: translations.py:668
msgid "Backward"
msgstr ""
#: translations.py:670
msgid "Saturate"
msgstr ""
#: translations.py:672 mlttransitions.py:163
msgid "Screen"
msgstr ""
#: translations.py:673 mlttransitions.py:161
msgid "Overlay"
msgstr ""
#: translations.py:674 mlttransitions.py:151
msgid "Darken"
msgstr ""
#: translations.py:675 mlttransitions.py:159
msgid "Lighten"
msgstr ""
#: translations.py:676
msgid "ColorDodge"
msgstr ""
#: translations.py:677
msgid "Colorburn"
msgstr ""
#: translations.py:678 mlttransitions.py:157
msgid "Hardlight"
msgstr ""
#: translations.py:679 mlttransitions.py:164
msgid "Softlight"
msgstr ""
#: translations.py:680 mlttransitions.py:152
msgid "Difference"
msgstr ""
#: translations.py:681
msgid "Exclusion"
msgstr ""
#: translations.py:682
msgid "HSLHue"
msgstr ""
#: translations.py:683
msgid "HSLSaturation"
msgstr ""
#: translations.py:684
msgid "HSLColor"
msgstr ""
#: translations.py:685
msgid "HSLLuminosity"
msgstr ""
#: translations.py:686
msgid "Cos"
msgstr ""
#: translations.py:687
msgid "Linear"
msgstr ""
#: translations.py:688
msgid "Radial"
msgstr ""
#: translations.py:689
msgid "Clear"
msgstr ""
#: translations.py:693
msgid "Square"
msgstr ""
#: translations.py:694
msgid "HDV"
msgstr ""
#: translations.py:695
msgid "DV/DVD Widescreen PAL"
msgstr ""
#: translations.py:696
msgid "DV/DVD Widescreen NTSC"
msgstr ""
#: translations.py:697
msgid "DV/DVD PAL"
msgstr ""
#: translations.py:698
msgid "DV/DVD NTSC"
msgstr ""
#: translations.py:699
msgid "Remove Lens Distortion"
msgstr ""
#: translations.py:700
msgid "Apply Lens Distortion"
msgstr ""
#: translations.py:701
msgid "Equidistant"
msgstr ""
#: translations.py:702
msgid "Orthographic"
msgstr ""
#: translations.py:703
msgid "Equiarea"
msgstr ""
#: translations.py:704
msgid "Stereographic"
msgstr ""
#: translations.py:705
msgid "Bilinear"
msgstr ""
#: translations.py:706
msgid "Bicubic Smooth"
msgstr ""
#: translations.py:707
msgid "Bicubic Sharp"
msgstr ""
#: translations.py:708
msgid "Spline"
msgstr ""
#: mlttransitions.py:85
msgid "Burst"
msgstr ""
#: mlttransitions.py:86
msgid "Checkerboard"
msgstr ""
#: mlttransitions.py:87
msgid "Circle From In"
msgstr ""
#: mlttransitions.py:88
msgid "Circle From Out"
msgstr ""
#: mlttransitions.py:89
msgid "Clock Left To Right"
msgstr ""
#: mlttransitions.py:90
msgid "Clock Right to Left"
msgstr ""
#: mlttransitions.py:91
msgid "Clock Symmetric"
msgstr ""
#: mlttransitions.py:92
msgid "Cloud"
msgstr ""
#: mlttransitions.py:93
msgid "Cross"
msgstr ""
#: mlttransitions.py:94
msgid "Diagonal 1"
msgstr ""
#: mlttransitions.py:95
msgid "Diagonal 2"
msgstr ""
#: mlttransitions.py:96
msgid "Diagonal 3"
msgstr ""
#: mlttransitions.py:97
msgid "Diagonal 4"
msgstr ""
#: mlttransitions.py:98
msgid "Flower"
msgstr ""
#: mlttransitions.py:99
msgid "Fogg"
msgstr ""
#: mlttransitions.py:100
msgid "Free Curves"
msgstr ""
#: mlttransitions.py:101
msgid "Free Stripes"
msgstr ""
#: mlttransitions.py:102
msgid "Heart"
msgstr ""
#: mlttransitions.py:103
msgid "Honeycomb"
msgstr ""
#: mlttransitions.py:104
msgid "Horizontal From Center"
msgstr ""
#: mlttransitions.py:105
msgid "Horizontal Left to Right"
msgstr ""
#: mlttransitions.py:106
msgid "Horizontal Right to Left"
msgstr ""
#: mlttransitions.py:107
msgid "Paint"
msgstr ""
#: mlttransitions.py:108
msgid "Patches"
msgstr ""
#: mlttransitions.py:109
msgid "Puzzle"
msgstr ""
#: mlttransitions.py:110
msgid "Rays"
msgstr ""
#: mlttransitions.py:111
msgid "Rectangle Bars"
msgstr ""
#: mlttransitions.py:112
msgid "Rectangle From In"
msgstr ""
#: mlttransitions.py:113
msgid "Rectangle From Out"
msgstr ""
#: mlttransitions.py:114
msgid "Rectangles"
msgstr ""
#: mlttransitions.py:115
msgid "Rings"
msgstr ""
#: mlttransitions.py:116
msgid "Sand"
msgstr ""
#: mlttransitions.py:117
msgid "Sphere"
msgstr ""
#: mlttransitions.py:118
msgid "Spiral Abstract"
msgstr ""
#: mlttransitions.py:119
msgid "Spiral Big"
msgstr ""
#: mlttransitions.py:120
msgid "Spiral Galaxy"
msgstr ""
#: mlttransitions.py:121
msgid "Spiral Medium"
msgstr ""
#: mlttransitions.py:122
msgid "Spiral"
msgstr ""
#: mlttransitions.py:123
msgid "Spots"
msgstr ""
#: mlttransitions.py:124 medialog.py:546
msgid "Star"
msgstr ""
#: mlttransitions.py:125
msgid "Stripes Horizontal Big"
msgstr ""
#: mlttransitions.py:126
msgid "Stripes Horizontal"
msgstr ""
#: mlttransitions.py:127
msgid "Stripes Horizontal Moving"
msgstr ""
#: mlttransitions.py:128
msgid "Stripes Vertical Big"
msgstr ""
#: mlttransitions.py:129
msgid "Stripes Vertical"
msgstr ""
#: mlttransitions.py:130
msgid "Torn frame"
msgstr ""
#: mlttransitions.py:131
msgid "Vertical Bottom to Top"
msgstr ""
#: mlttransitions.py:132
msgid "Vertical From Center"
msgstr ""
#: mlttransitions.py:133
msgid "Vertical Top to Bottom"
msgstr ""
#: mlttransitions.py:134
msgid "Wood"
msgstr ""
#: mlttransitions.py:137 mlttransitions.py:200
msgid "Dissolve"
msgstr ""
#: mlttransitions.py:138
msgid "Picture in Picture"
msgstr ""
#: mlttransitions.py:140
msgid "Affine Blend"
msgstr ""
#: mlttransitions.py:141
msgid "Blend"
msgstr ""
#: mlttransitions.py:149
msgid "Burn"
msgstr ""
#: mlttransitions.py:150
msgid "Color only"
msgstr ""
#: mlttransitions.py:153
msgid "Divide"
msgstr ""
#: mlttransitions.py:154
msgid "Dodge"
msgstr ""
#: mlttransitions.py:155
msgid "Grain extract"
msgstr ""
#: mlttransitions.py:156
msgid "Grain merge"
msgstr ""
#: mlttransitions.py:166
msgid "Value"
msgstr ""
#: mlttransitions.py:171
msgid "LumaToAlpha"
msgstr ""
#: mlttransitions.py:172
msgid "Alpha XOR"
msgstr ""
#: mlttransitions.py:173
msgid "Alpha Out"
msgstr ""
#: mlttransitions.py:174
msgid "Alpha In"
msgstr ""
#: mlttransitions.py:176
msgid "Wipe/Translate"
msgstr ""
#: mlttransitions.py:177
msgid "Wipe Clip Length"
msgstr ""
#: mlttransitions.py:202
msgid "Color Dip"
msgstr ""
#: propertyeditorbuilder.py:526
msgid "Preset Luma"
msgstr ""
#: propertyeditorbuilder.py:527
msgid "User Luma"
msgstr ""
#: propertyeditorbuilder.py:529
msgid "Select Luma File"
msgstr ""
#: propertyeditorbuilder.py:538
msgid "Wipe Luma files"
msgstr ""
#: propertyeditorbuilder.py:544
msgid "Luma File:"
msgstr ""
#: propertyeditorbuilder.py:668
msgid "Select File"
msgstr ""
#: propertyeditorbuilder.py:708
msgid "Select Image Producing File"
msgstr ""
#: propertyeditorbuilder.py:739 propertyeditorbuilder.py:794
msgid "Nothing"
msgstr ""
#: propertyeditorbuilder.py:739 propertyeditorbuilder.py:794
msgid "Deinterlace"
msgstr ""
#: propertyeditorbuilder.py:739 propertyeditorbuilder.py:794
msgid "Both"
msgstr ""
#: propertyeditorbuilder.py:748 propertyeditorbuilder.py:803
msgid "Force"
msgstr ""
#: propertyeditorbuilder.py:753 propertyeditorbuilder.py:808
msgid "Align"
msgstr ""
#: propertyeditorbuilder.py:867
msgid "Keyframes"
msgstr ""
#: propertyeditorbuilder.py:871
msgid "Curve Points"
msgstr ""
#: propertyeditorbuilder.py:875
msgid "Lauch RotoMask editor"
msgstr ""
#: propertyeditorbuilder.py:879
msgid "RotoMask info"
msgstr ""
#: keyframeeditor.py:627 kftoolmode.py:1346
msgid "Delete all but first Keyframe before Clip Range"
msgstr ""
#: keyframeeditor.py:633 kftoolmode.py:1352
msgid "Set Keyframe at Frame 0 to value of next Keyframe"
msgstr ""
#: keyframeeditor.py:635 kftoolmode.py:1341 kftoolmode.py:1364
#: kftoolmode.py:1375
msgid "No Edit Actions currently available"
msgstr ""
#: keyframeeditor.py:650 kftoolmode.py:1370
msgid "Delete all Keyframes after Clip Range"
msgstr ""
#: keyframeeditor.py:738
msgid "Add Keyframe"
msgstr ""
#: keyframeeditor.py:739
msgid "Delete Keyframe"
msgstr ""
#: keyframeeditor.py:740
msgid "Previous Keyframe"
msgstr ""
#: keyframeeditor.py:741
msgid "Next Keyframe"
msgstr ""
#: keyframeeditor.py:744
msgid "Move Keyframe 1 Frame Back"
msgstr ""
#: keyframeeditor.py:745
msgid "Move Keyframe 1 Frame Forward"
msgstr ""
#: keyframeeditor.py:746
msgid "Add Fade In"
msgstr ""
#: keyframeeditor.py:747
msgid "Add Fade Out"
msgstr ""
#: keyframeeditor.py:821
msgid "View:"
msgstr ""
#: keyframeeditor.py:850
msgid "Reset Geometry"
msgstr ""
#: keyframeeditor.py:851
msgid "Geometry to Original Aspect Ratio"
msgstr ""
#: keyframeeditor.py:852
msgid "Center Horizontal"
msgstr ""
#: keyframeeditor.py:853
msgid "Center Vertical"
msgstr ""
#: keyframeeditor.py:1854 keyframeeditor.py:1893
msgid "X:"
msgstr ""
#: keyframeeditor.py:1855 keyframeeditor.py:1894
msgid "Y:"
msgstr ""
#: keyframeeditor.py:1856 profilesmanager.py:133
msgid "Width:"
msgstr ""
#: keyframeeditor.py:1857 profilesmanager.py:134
msgid "Height:"
msgstr ""
#: keyframeeditor.py:1895
msgid "X scale:"
msgstr ""
#: keyframeeditor.py:1896
msgid "Y scale:"
msgstr ""
#: keyframeeditor.py:1897
msgid "Rotation:"
msgstr ""
#: middlebar.py:139
msgid "Zoom In - Mouse Middle Scroll"
msgstr ""
#: middlebar.py:139
msgid "Zoom Out - Mouse Middle Scroll"
msgstr ""
#: middlebar.py:139
msgid "Zoom Length - Mouse Middle Click"
msgstr ""
#: middlebar.py:146
msgid ""
"Add Rendered Transition - 2 clips selected\n"
"Add Rendered Fade - 1 clip selected"
msgstr ""
#: middlebar.py:146
msgid ""
"Cut Active Tracks - X\n"
"Cut All Tracks - Shift + X"
msgstr ""
#: middlebar.py:155
msgid "Splice Out - Delete"
msgstr ""
#: middlebar.py:155
msgid "Lift - Control + Delete"
msgstr ""
#: middlebar.py:155
msgid "Ripple Delete"
msgstr ""
#: middlebar.py:155
msgid "Range Delete"
msgstr ""
#: middlebar.py:162
msgid "Resync Selected"
msgstr ""
#: middlebar.py:171 shortcuts.py:202
msgid "Overwrite Range"
msgstr ""
#: middlebar.py:171
msgid "Overwrite Clip - T"
msgstr ""
#: middlebar.py:171
msgid "Insert Clip - Y"
msgstr ""
#: middlebar.py:171
msgid "Append Clip - U"
msgstr ""
#: middlebar.py:178
msgid "Undo - Ctrl + Z"
msgstr ""
#: middlebar.py:178
msgid "Redo - Ctrl + Y"
msgstr ""
#: middlebar.py:187
msgid "G'Mic Effects"
msgstr ""
#: middlebar.py:193
msgid ""
"Audio Mixer(not available)\n"
"Titler"
msgstr ""
#: medialog.py:322
msgid "New Group..."
msgstr ""
#: medialog.py:323
msgid "New Group From Selected..."
msgstr ""
#: medialog.py:327
msgid "Rename Current Group..."
msgstr ""
#: medialog.py:333
msgid "Move Selected Items To Group"
msgstr ""
#: medialog.py:336
msgid "No Groups"
msgstr ""
#: medialog.py:349
msgid "Delete Current Group"
msgstr ""
#: medialog.py:355
msgid "Sort by"
msgstr ""
#: medialog.py:358
msgid "Time"
msgstr ""
#: medialog.py:364 medialog.py:552
msgid "File Name"
msgstr ""
#: medialog.py:369 medialog.py:550
msgid "Comment"
msgstr ""
#: medialog.py:476
msgid "Group "
msgstr ""
#: medialog.py:548 projectinfogui.py:128
msgid "Event"
msgstr ""
#: medialog.py:554
msgid "Mark In"
msgstr ""
#: medialog.py:556
msgid "Mark Out"
msgstr ""
#: medialog.py:558 projectinfogui.py:126
msgid "Date"
msgstr ""
#: medialog.py:721
msgid "Use Comments as Clip Names"
msgstr ""
#: medialog.py:753
msgid "Display starred ranges"
msgstr ""
#: medialog.py:754
msgid "Display non-starred ranges"
msgstr ""
#: medialog.py:755
msgid "Set selected ranges starred"
msgstr ""
#: medialog.py:756
msgid "Set selected ranges non-starred"
msgstr ""
#: medialog.py:757
msgid "Log current marked range"
msgstr ""
#: medialog.py:758
msgid "Delete selected ranges"
msgstr ""
#: medialog.py:759
msgid "Insert selected ranges on Timeline"
msgstr ""
#: medialog.py:760
msgid "Append displayed ranges on Timeline"
msgstr ""
#: medialog.py:777
msgid "All Items"
msgstr ""
#: medialog.py:784
msgid "Select viewed Range Log Items Group"
msgstr ""
#: projectinfogui.py:43
msgid "Name"
msgstr ""
#: projectinfogui.py:69
msgid ""
"A Project contains one or more Sequences of edited media and a "
"collection of media files stored in Bins."
msgstr ""
#: projectinfogui.py:77
msgid ""
"Profile determines frame rate per second, image size in pixels and "
"pixel aspect ratio for all Sequences in Project ."
msgstr ""
#: projectinfogui.py:102
msgid "Project Events"
msgstr ""
#: projectinfogui.py:130
msgid "Data"
msgstr ""
#: tools/titler.py:86
msgid "Titler is already open"
msgstr ""
#: tools/titler.py:87
msgid "Only single instance of Titler can be opened."
msgstr ""
#: tools/titler.py:362
msgid "Opacity:"
msgstr ""
#: tools/titler.py:363
msgid "X Off:"
msgstr ""
#: tools/titler.py:364
msgid "Y Off:"
msgstr ""
#: tools/titler.py:412
msgid "Load Layers"
msgstr ""
#: tools/titler.py:414
msgid "Save Layers"
msgstr ""
#: tools/titler.py:416
msgid "Clear All"
msgstr ""
#: tools/titler.py:501
msgid "Font"
msgstr ""
#: tools/titler.py:502
msgid "Outline"
msgstr ""
#: tools/titler.py:503
msgid "Shadow"
msgstr ""
#: tools/titler.py:506
msgid "Layer Text"
msgstr ""
#: tools/titler.py:509
msgid "Layers"
msgstr ""
#: tools/titler.py:515
msgid "Keep Layers When Closed"
msgstr ""
#: tools/titler.py:520
msgid "Open Saved Title In Bin"
msgstr ""
#: tools/titler.py:527
msgid "Save Title Graphic"
msgstr ""
#: rendergui.py:46
msgid "Render Progress"
msgstr ""
#: rendergui.py:74
msgid ""
"Project and Render Profile FPS values are not same. Rendered file may have A/"
"V sync issues."
msgstr ""
#: rendergui.py:101
msgid "Render range not defined!"
msgstr ""
#: rendergui.py:102
msgid ""
"Define render range using Mark In and Mark Out points\n"
"or select range option 'Sequence length' to start rendering."
msgstr ""
#: rendergui.py:106
msgid "Load Render Args File"
msgstr ""
#: rendergui.py:120
msgid "Save Render Args As"
msgstr ""
#: rendergui.py:174
msgid "Render Slow/Fast Motion Video File"
msgstr ""
#: rendergui.py:179 rendergui.py:325
msgid "Source Media File: "
msgstr ""
#: rendergui.py:186 rendergui.py:187 rendergui.py:332 rendergui.py:333
msgid "not set"
msgstr ""
#: rendergui.py:207 rendergui.py:353
msgid "Select Target Folder"
msgstr ""
#: rendergui.py:211 rendergui.py:357
msgid "Speed %:"
msgstr ""
#: rendergui.py:222 rendergui.py:394
msgid "Full Source Length"
msgstr ""
#: rendergui.py:227 rendergui.py:399
msgid "Source Mark In to Mark Out"
msgstr ""
#: rendergui.py:246
msgid "Rendered Length:"
msgstr ""
#: rendergui.py:275 rendergui.py:422
msgid "Source Mark In: "
msgstr ""
#: rendergui.py:276 rendergui.py:423
msgid "Source Mark Out: "
msgstr ""
#: rendergui.py:281 rendergui.py:428
msgid "Target File:"
msgstr ""
#: rendergui.py:282 rendergui.py:429
msgid "Target Folder:"
msgstr ""
#: rendergui.py:283 rendergui.py:430
msgid "Target Profile:"
msgstr ""
#: rendergui.py:284 rendergui.py:431
msgid "Target Encoding:"
msgstr ""
#: rendergui.py:285 rendergui.py:432
msgid "Target Quality:"
msgstr ""
#: rendergui.py:287 rendergui.py:434 rendergui.py:623
msgid "Render Range:"
msgstr ""
#: rendergui.py:320
msgid "Render Reverse Motion Video File"
msgstr ""
#: rendergui.py:435
msgid "Rendered Clip Length:"
msgstr ""
#: rendergui.py:468 tools/toolsencoding.py:172
msgid "Select Render quality"
msgstr ""
#: rendergui.py:486
msgid "Select audio sample frequency"
msgstr ""
#: rendergui.py:510 tools/toolsencoding.py:231
msgid "Select Render encoding"
msgstr ""
#: rendergui.py:547 tools/toolsencoding.py:155
msgid "Select render profile"
msgstr ""
#: rendergui.py:574
msgid "Full Length"
msgstr ""
#: rendergui.py:575
msgid "Marked Range"
msgstr ""
#: rendergui.py:583 tools/toolsencoding.py:270
msgid "File"
msgstr ""
#: rendergui.py:584 tools/toolsencoding.py:271
msgid "Render Profile"
msgstr ""
#: rendergui.py:587 rendergui.py:599 rendergui.py:609
#: tools/toolsencoding.py:272
msgid "Encoding Format"
msgstr ""
#: rendergui.py:589 tools/toolsencoding.py:273
msgid "Render Type"
msgstr ""
#: rendergui.py:611 rendergui.py:1088
msgid "Render Args"
msgstr ""
#: rendergui.py:615
msgid "Open File in Bin:"
msgstr ""
#: rendergui.py:692 tools/toolsencoding.py:60
msgid "Select folder to place rendered file in"
msgstr ""
#: rendergui.py:693 tools/toolsencoding.py:61
msgid "Give name for rendered file"
msgstr ""
#: rendergui.py:700 tools/toolsencoding.py:68
msgid "Presets:"
msgstr ""
#: rendergui.py:703 tools/toolsencoding.py:71
msgid "User Defined"
msgstr ""
#: rendergui.py:704 tools/toolsencoding.py:72
msgid "Preset File type"
msgstr ""
#: rendergui.py:721 rendergui.py:798 rendergui.py:922
msgid "Use Project Profile:"
msgstr ""
#: rendergui.py:722 rendergui.py:799 rendergui.py:923
msgid "Render using args:"
msgstr ""
#: rendergui.py:737
msgid "Select used project profile for rendering"
msgstr ""
#: rendergui.py:738
msgid "Render profile info"
msgstr ""
#: rendergui.py:824 rendergui.py:1049
msgid "Load Selection"
msgstr ""
#: rendergui.py:829 rendergui.py:1052
msgid "Ext.:"
msgstr ""
#: rendergui.py:864 rendergui.py:961
msgid "Render using key=value rendering options"
msgstr ""
#: rendergui.py:865 rendergui.py:1059
msgid "Load render options from currently selected encoding"
msgstr ""
#: rendergui.py:866 rendergui.py:1060
msgid "Edit render options"
msgstr ""
#: rendergui.py:867 rendergui.py:963
msgid "Save Render Args into a text file"
msgstr ""
#: rendergui.py:868 rendergui.py:964
msgid "Load Render Args from a text file"
msgstr ""
#: rendergui.py:941
msgid "Edit Args:"
msgstr ""
#: rendergui.py:1071
msgid "Set Args"
msgstr ""
#: profilesmanager.py:48 proxyediting.py:221
msgid "Close Manager"
msgstr ""
#: profilesmanager.py:73 tools/batchrendering.py:675
msgid "Delete Selected"
msgstr ""
#: profilesmanager.py:83
msgid "Load Profile Values"
msgstr ""
#: profilesmanager.py:118
msgid "Save New Profile"
msgstr ""
#: profilesmanager.py:130
msgid "Description.:"
msgstr ""
#: profilesmanager.py:131
msgid "Frame rate num.:"
msgstr ""
#: profilesmanager.py:132
msgid "Frame rate den.:"
msgstr ""
#: profilesmanager.py:135
msgid "Sample aspect num.:"
msgstr ""
#: profilesmanager.py:136
msgid "Sample aspect den.:"
msgstr ""
#: profilesmanager.py:137
msgid "Display aspect num.:"
msgstr ""
#: profilesmanager.py:138
msgid "Display aspect den.:"
msgstr ""
#: profilesmanager.py:167
msgid "Create User Profile"
msgstr ""
#: profilesmanager.py:168
msgid "User Profiles"
msgstr ""
#: profilesmanager.py:176
msgid "Visible"
msgstr ""
#: profilesmanager.py:178
msgid "Hide Selected"
msgstr ""
#: profilesmanager.py:180
msgid "Hidden"
msgstr ""
#: profilesmanager.py:182
msgid "Unhide Selected"
msgstr ""
#: profilesmanager.py:211
msgid "Factory Profiles"
msgstr ""
#: profilesmanager.py:217
msgid "User "
msgstr ""
#: profilesmanager.py:259 profilesmanager.py:267
msgid "Profile '"
msgstr ""
#: profilesmanager.py:259
msgid "' already exists!"
msgstr ""
#: profilesmanager.py:260
msgid "Delete profile and save again."
msgstr ""
#: profilesmanager.py:267
msgid "' saved."
msgstr ""
#: profilesmanager.py:268
msgid "You can now create a new project using the new profile."
msgstr ""
#: profilesmanager.py:280
msgid "Confirm user profile delete"
msgstr ""
#: profilesmanager.py:281 tools/batchrendering.py:758
msgid "This operation cannot be undone."
msgstr ""
#: preferenceswindow.py:47
msgid "Editor Preferences"
msgstr ""
#: preferenceswindow.py:64
msgid "Editing"
msgstr ""
#: preferenceswindow.py:67
msgid "Performance"
msgstr ""
#: preferenceswindow.py:84
msgid "Restart required for some setting changes to take effect."
msgstr ""
#: preferenceswindow.py:85
msgid "If requested change is not in effect, restart application."
msgstr ""
#: preferenceswindow.py:122
msgid "Absolute paths first, relative second"
msgstr ""
#: preferenceswindow.py:123
msgid "Relative paths first, absolute second"
msgstr ""
#: preferenceswindow.py:124
msgid "Absolute paths only"
msgstr ""
#: preferenceswindow.py:128
msgid "Default Profile:"
msgstr ""
#: preferenceswindow.py:129
msgid "Remember last media directory"
msgstr ""
#: preferenceswindow.py:130
msgid "Undo stack size:"
msgstr ""
#: preferenceswindow.py:131
msgid "Remember last render directory"
msgstr ""
#: preferenceswindow.py:132
msgid "Autosave for crash recovery every:"
msgstr ""
#: preferenceswindow.py:133
msgid "Media look-up order on load:"
msgstr ""
#: preferenceswindow.py:165
msgid "Zoom, Control to Scroll Horizontal"
msgstr ""
#: preferenceswindow.py:166
msgid "Scroll Horizontal, Control to Zoom"
msgstr ""
#: preferenceswindow.py:173
msgid "Scroll Up Forward"
msgstr ""
#: preferenceswindow.py:174
msgid "Scroll Down Forward"
msgstr ""
#: preferenceswindow.py:181
msgid "On Double Click"
msgstr ""
#: preferenceswindow.py:182
msgid "On Single Click"
msgstr ""
#: preferenceswindow.py:190
msgid "Graphics default length:"
msgstr ""
#: preferenceswindow.py:191
msgid "Cover Transition/Fade clips on delete if possible"
msgstr ""
#: preferenceswindow.py:193
msgid "Mouse Middle Button Scroll Action:"
msgstr ""
#: preferenceswindow.py:194
msgid "Mouse Horizontal Scroll Direction:"
msgstr ""
#: preferenceswindow.py:195
msgid "Hide file extensions when importing Clips"
msgstr ""
#: preferenceswindow.py:196
msgid "Open Clip in Effects Editor"
msgstr ""
#: preferenceswindow.py:264
msgid "Center Current Frame on Playback Stop"
msgstr ""
#: preferenceswindow.py:265
msgid "Center Current Frame after Up/Down Arrow"
msgstr ""
#: preferenceswindow.py:267
msgid "Enable single Play/Pause button"
msgstr ""
#: preferenceswindow.py:269
msgid "Fast Forward / Reverse Speed for Shift Key:"
msgstr ""
#: preferenceswindow.py:270
msgid ""
"Speed of Forward / Reverse will be multiplied by this value if Shift Key is "
"held (Only using KEYS).\n"
"Enabling multiple modifier keys will multiply the set values.\n"
"E.g. if Shift is set to "
msgstr ""
#: preferenceswindow.py:275
msgid "Fast Forward / Reverse Speed for Control Key:"
msgstr ""
#: preferenceswindow.py:276
msgid ""
"Speed of Forward / Reverse will be multiplied by this value if Ctrl Key is "
"held (Only using KEYS)."
msgstr ""
#: preferenceswindow.py:277
msgid "Fast Forward / Reverse Speed for Caps Lock Key:"
msgstr ""
#: preferenceswindow.py:278
msgid ""
"Speed of Forward / Reverse will be multiplied by this value if Caps Lock is "
"set (Only using KEYS)."
msgstr ""
#: preferenceswindow.py:279
msgid "Move Timeline to follow Playback"
msgstr ""
#: preferenceswindow.py:280
msgid "Loop Media Clips on Monitor"
msgstr ""
#: preferenceswindow.py:312
msgid "None"
msgstr ""
#: preferenceswindow.py:313
msgid "English"
msgstr ""
#: preferenceswindow.py:314
msgid "Chinese, Simplified"
msgstr ""
#: preferenceswindow.py:315
msgid "Chinese, Traditional"
msgstr ""
#: preferenceswindow.py:316
msgid "Czech"
msgstr ""
#: preferenceswindow.py:317
msgid "French"
msgstr ""
#: preferenceswindow.py:318
msgid "German"
msgstr ""
#: preferenceswindow.py:319
msgid "Hungarian"
msgstr ""
#: preferenceswindow.py:320
msgid "Italian"
msgstr ""
#: preferenceswindow.py:321
msgid "Polish"
msgstr ""
#: preferenceswindow.py:322
msgid "Russian"
msgstr ""
#: preferenceswindow.py:323
msgid "Spanish"
msgstr ""
#: preferenceswindow.py:324
msgid "Ukranian"
msgstr ""
#: preferenceswindow.py:339
msgid "Glass"
msgstr ""
#: preferenceswindow.py:340
msgid "Simple"
msgstr ""
#: preferenceswindow.py:341
msgid "No Decorations"
msgstr ""
#: preferenceswindow.py:345
msgid "Flowblade Theme"
msgstr ""
#: preferenceswindow.py:346
msgid "Dark Theme"
msgstr ""
#: preferenceswindow.py:347
msgid "Light Theme"
msgstr ""
#: preferenceswindow.py:357
msgid "Display All Levels"
msgstr ""
#: preferenceswindow.py:358
msgid "Display Levels On Request"
msgstr ""
#: preferenceswindow.py:373
msgid "Normal - 50px, 25px"
msgstr ""
#: preferenceswindow.py:374
msgid "Double for HiDPI - 100px, 50px"
msgstr ""
#: preferenceswindow.py:379
msgid "3 panels if width (1450px+) available"
msgstr ""
#: preferenceswindow.py:380
msgid "2 panels always"
msgstr ""
#: preferenceswindow.py:386
msgid "Full Display area: "
msgstr ""
#: preferenceswindow.py:390
msgid "Monitor "
msgstr ""
#: preferenceswindow.py:395
msgid "Application window mode:"
msgstr ""
#: preferenceswindow.py:397
msgid "Force Language:"
msgstr ""
#: preferenceswindow.py:398
msgid "Display splash screen"
msgstr ""
#: preferenceswindow.py:399
msgid "Buttons style:"
msgstr ""
#: preferenceswindow.py:400
msgid "Theme request, icons and colors:"
msgstr ""
#: preferenceswindow.py:401
msgid "Theme detection fail fallback colors:"
msgstr ""
#: preferenceswindow.py:402
msgid "Default audio levels display:"
msgstr ""
#: preferenceswindow.py:403
msgid "Tracks Heights:"
msgstr ""
#: preferenceswindow.py:405
msgid "Show Full File names"
msgstr ""
#: preferenceswindow.py:406
msgid "Top row layout:"
msgstr ""
#: preferenceswindow.py:408
msgid "Do GUI layout based on:"
msgstr ""
#: preferenceswindow.py:441
msgid ""
"Changing these values may cause problems with playback and rendering.\n"
"The safe values are Render Threads:1, Allow Frame Dropping: No."
msgstr ""
#: preferenceswindow.py:452
msgid "Between 1 and the number of CPU Cores"
msgstr ""
#: preferenceswindow.py:453
msgid "Allow Frame Dropping for real-time rendering, when needed"
msgstr ""
#: preferenceswindow.py:457
msgid "Render Threads:"
msgstr ""
#: preferenceswindow.py:458
msgid "Allow Frame Dropping"
msgstr ""
#: tools/batchrendering.py:306
msgid "Render Item Project File Copy failed!"
msgstr ""
#: tools/batchrendering.py:378 tools/batchrendering.py:775
msgid "Error loading render queue items!"
msgstr ""
#: tools/batchrendering.py:379 tools/batchrendering.py:776
msgid "Message:\n"
msgstr ""
#: tools/batchrendering.py:397
msgid "Batch Render Queue already running!"
msgstr ""
#: tools/batchrendering.py:399
msgid "Batch Render Queue application was detected in session dbus."
msgstr ""
#: tools/batchrendering.py:421
msgid "Application is rendering and cannot be closed!"
msgstr ""
#: tools/batchrendering.py:422
msgid "Stop rendering before closing the application."
msgstr ""
#: tools/batchrendering.py:455
msgid " datafile load failed with "
msgstr ""
#: tools/batchrendering.py:463
msgid " project file load failed with "
msgstr ""
#: tools/batchrendering.py:585
msgid "Queued"
msgstr ""
#: tools/batchrendering.py:587
msgid "Rendering"
msgstr ""
#: tools/batchrendering.py:589
msgid "Finished"
msgstr ""
#: tools/batchrendering.py:591
msgid "Unqueued"
msgstr ""
#: tools/batchrendering.py:593
msgid "Aborted"
msgstr ""
#: tools/batchrendering.py:650 tools/batchrendering.py:1319
msgid "Estimated Left:"
msgstr ""
#: tools/batchrendering.py:651
msgid "Current Render:"
msgstr ""
#: tools/batchrendering.py:652 tools/batchrendering.py:1321 proxyediting.py:367
msgid "Elapsed:"
msgstr ""
#: tools/batchrendering.py:663
msgid "Items Rendered:"
msgstr ""
#: tools/batchrendering.py:665
msgid "Render Started:"
msgstr ""
#: tools/batchrendering.py:671
msgid "Not Rendering"
msgstr ""
#: tools/batchrendering.py:679
msgid "Delete Finished"
msgstr ""
#: tools/batchrendering.py:684
msgid "Reload Queue"
msgstr ""
#: tools/batchrendering.py:695 tools/batchrendering.py:1331
msgid "Stop Render"
msgstr ""
#: tools/batchrendering.py:731
msgid "Flowblade Batch Render"
msgstr ""
#: tools/batchrendering.py:757
msgid "Delete "
msgstr ""
#: tools/batchrendering.py:757
msgid " item(s) from render queue?"
msgstr ""
#: tools/batchrendering.py:788
msgid "Multiple items with same render target file!"
msgstr ""
#: tools/batchrendering.py:790
msgid ""
"Later items will render on top of earlier items if this queue is rendered.\n"
msgstr ""
#: tools/batchrendering.py:791
msgid ""
"Delete or unqueue some items with same paths:\n"
"\n"
msgstr ""
#: tools/batchrendering.py:793
msgid " items with path: "
msgstr ""
#: tools/batchrendering.py:892
msgid "Project/Sequence"
msgstr ""
#: tools/batchrendering.py:893
msgid "Status"
msgstr ""
#: tools/batchrendering.py:894
msgid "Render File"
msgstr ""
#: tools/batchrendering.py:895
msgid "Render Time"
msgstr ""
#: tools/batchrendering.py:987
msgid "Save Render Item Project As"
msgstr ""
#: tools/batchrendering.py:1024 proxyediting.py:335
msgid "Using Original Media"
msgstr ""
#: tools/batchrendering.py:1026 proxyediting.py:333
msgid "Using Proxy Media"
msgstr ""
#: tools/batchrendering.py:1033
msgid "Encoding:"
msgstr ""
#: tools/batchrendering.py:1034
msgid "Quality:"
msgstr ""
#: tools/batchrendering.py:1035
msgid "Audio Encoding:"
msgstr ""
#: tools/batchrendering.py:1036
msgid "Use User Args:"
msgstr ""
#: tools/batchrendering.py:1037
msgid "Start:"
msgstr ""
#: tools/batchrendering.py:1038
msgid "End:"
msgstr ""
#: tools/batchrendering.py:1040
msgid "Render Profile Name:"
msgstr ""
#: tools/batchrendering.py:1041
msgid "Render Profile:"
msgstr ""
#: tools/batchrendering.py:1042
msgid "Proxy Mode:"
msgstr ""
#: tools/batchrendering.py:1058 tools/batchrendering.py:1066
msgid "Render Properties"
msgstr ""
#: tools/batchrendering.py:1065
msgid "Save Item Project As..."
msgstr ""
#: tools/batchrendering.py:1356
msgid "Flowblade Timeline Render"
msgstr ""
#: proxyediting.py:242
msgid "Project Image Size"
msgstr ""
#: proxyediting.py:243
msgid "Half Project Image Size"
msgstr ""
#: proxyediting.py:244
msgid "Quarter Project Image Size"
msgstr ""
#: proxyediting.py:260
msgid "Proxy Encoding"
msgstr ""
#: proxyediting.py:272
msgid "Proxy Stats:"
msgstr ""
#: proxyediting.py:273
msgid " proxy file(s) for "
msgstr ""
#: proxyediting.py:273
msgid " video file(s)"
msgstr ""
#: proxyediting.py:276
msgid "Current Proxy Mode:"
msgstr ""
#: proxyediting.py:283 proxyediting.py:347
msgid "Press Button to Change Mode"
msgstr ""
#: proxyediting.py:285
msgid "Use Proxy Media"
msgstr ""
#: proxyediting.py:286
msgid "Use Original Media"
msgstr ""
#: proxyediting.py:307
msgid "Project Proxy Mode"
msgstr ""
#: proxyediting.py:353
msgid "Creating Proxy Files"
msgstr ""
#: proxyediting.py:356 tools/gmic.py:814 shortcuts.py:193
msgid "Stop"
msgstr ""
#: proxyediting.py:368
msgid "Current Media File:"
msgstr ""
#: proxyediting.py:369
msgid "Rendering Item:"
msgstr ""
#: proxyediting.py:409
msgid "Proxy Render Info"
msgstr ""
#: proxyediting.py:424
msgid "Nothing will be rendered"
msgstr ""
#: proxyediting.py:425
msgid ""
"No video files were selected.\n"
"Only video files can have proxy files."
msgstr ""
#: proxyediting.py:433
msgid "Do Render Action"
msgstr ""
#: proxyediting.py:438
msgid "Proxies exist that were created by this and other projects for "
msgstr ""
#: proxyediting.py:438 proxyediting.py:441 proxyediting.py:444
msgid " file(s).\n"
msgstr ""
#: proxyediting.py:441
msgid "Proxies have already been created for "
msgstr ""
#: proxyediting.py:444
msgid "Proxies exist that were created by other projects for "
msgstr ""
#: proxyediting.py:447 proxyediting.py:450
msgid "You are trying to create proxies for "
msgstr ""
#: proxyediting.py:447
msgid " non-video file(s).\n"
msgstr ""
#: proxyediting.py:450
msgid " proxy file(s).\n"
msgstr ""
#: proxyediting.py:452
msgid "There are some issues with proxy render request"
msgstr ""
#: proxyediting.py:458
msgid ""
"Rerendering proxies currently not possible!\n"
"Change to 'Use Original Media' mode to rerender proxies."
msgstr ""
#: proxyediting.py:464
msgid "Render Unrendered Possible & Use existing"
msgstr ""
#: proxyediting.py:466
msgid "Rerender All Possible"
msgstr ""
#: proxyediting.py:469
msgid "Select Render Action: "
msgstr ""
#: proxyediting.py:671
msgid "Converting Project to Use Proxy Media"
msgstr ""
#: proxyediting.py:684
msgid "Converting to Use Original Media"
msgstr ""
#: tlineaction.py:195
msgid "Confirm split to new Sequence at Playhead position"
msgstr ""
#: tlineaction.py:196
msgid ""
"This will create a new sequence from the part after playhead. That part will "
"be removed from\n"
"your current active sequence.\n"
"\n"
"The newly created sequence will be opened as current sequence."
msgstr ""
#: tlineaction.py:419
msgid "Fade/Transition cover delete failed!"
msgstr ""
#: tlineaction.py:420
msgid ""
"There wasn't enough material available in adjacent clips.\n"
"A normal Splice Out was done instead."
msgstr ""
#: tlineaction.py:541
msgid "Can't do Ripple Delete!"
msgstr ""
#: tlineaction.py:542
msgid ""
"Seleted Ripple Delete would cause an overwrite and that is not permitted for "
"this edit action.\n"
"\n"
"Overwrite would happen on at track "
msgstr ""
#: tlineaction.py:593
msgid "No Clips are selected!"
msgstr ""
#: tlineaction.py:594
msgid "You need to select clips to overwrite to perform this edit."
msgstr ""
#: tlineaction.py:712 tlineaction.py:1737
msgid "3 point edit not defined!"
msgstr ""
#: tlineaction.py:713 tlineaction.py:737
msgid ""
"You need to set Timeline Range using Mark In and Mark Out buttons\n"
"to perform this edit."
msgstr ""
#: tlineaction.py:736
msgid "Timeline Range not set!"
msgstr ""
#: tlineaction.py:771
msgid "Origin clip not found!"
msgstr ""
#: tlineaction.py:772
msgid ""
"Clip used to create this Compositor has been removed\n"
"or moved to different track."
msgstr ""
#: tlineaction.py:1023 tlineaction.py:1291 tlineaction.py:1390
msgid "Rendering "
msgstr ""
#: tlineaction.py:1109
msgid "Rerendering "
msgstr ""
#: tlineaction.py:1138
msgid ""
"To create a rendered transition you need enough media overlap from both "
"clips!\n"
"\n"
msgstr ""
#: tlineaction.py:1143
msgid "FIRST CLIP MEDIA OVERLAP: "
msgstr ""
#: tlineaction.py:1144 tlineaction.py:1152
msgid "Available "
msgstr ""
#: tlineaction.py:1144 tlineaction.py:1152
msgid " frame(s), "
msgstr ""
#: tlineaction.py:1145 tlineaction.py:1153
msgid "Required "
msgstr ""
#: tlineaction.py:1145
msgid " frame(s)"
msgstr ""
#: tlineaction.py:1151
msgid "SECOND CLIP MEDIA OVERLAP: "
msgstr ""
#: tlineaction.py:1153
msgid " frame(s) "
msgstr ""
#: tlineaction.py:1160
msgid "Current situation, not enought media overlap:"
msgstr ""
#: tlineaction.py:1162
msgid "You need more media overlap:"
msgstr ""
#: tlineaction.py:1191
msgid "More media overlap needed to create transition!"
msgstr ""
#: tlineaction.py:1210
msgid "Only Video Track mix / fades available"
msgstr ""
#: tlineaction.py:1211
msgid ""
"Unfortunately rendered mixes and fades can currently\n"
"only be applied on clips on Video Tracks."
msgstr ""
#: tlineaction.py:1248
msgid ""
"Clip is too short for the requested fade:\n"
"\n"
msgstr ""
#: tlineaction.py:1249
msgid "Clip Length: "
msgstr ""
#: tlineaction.py:1249 tlineaction.py:1250
msgid " frame(s)\n"
msgstr ""
#: tlineaction.py:1250
msgid "Fade Length: "
msgstr ""
#: tlineaction.py:1251
msgid "Clip is too short!"
msgstr ""
#: tlineaction.py:1472
msgid "Rerender all Rendered Transitions / Fades"
msgstr ""
#: tlineaction.py:1516
msgid "Rendering item "
msgstr ""
#: tlineaction.py:1703 tlineaction.py:1708
msgid "Can't rerender this fade / transition."
msgstr ""
#: tlineaction.py:1704
msgid ""
"This fade / transition was created with Flowblade <= 1.14 and does not have "
"the necessary data embedded.\n"
"Rerendering works with fades/transitions created with Flowblade >= 1.16."
msgstr ""
#: tlineaction.py:1709
msgid ""
"The clip/s used to create this fade / transition are no longer available on "
"the timeline."
msgstr ""
#: tlineaction.py:1727
msgid "No Clip loaded into Monitor"
msgstr ""
#: tlineaction.py:1728
msgid "Can't do the requested edit because there is no Clip in Monitor."
msgstr ""
#: tlineaction.py:1732
msgid "Defined range in Monitor Clip is too short"
msgstr ""
#: tlineaction.py:1733
msgid ""
"Can't do the requested edit because Mark In -> Mark Out Range or Clip is too "
"short."
msgstr ""
#: tlineaction.py:1738
msgid ""
"You need to set Mark In and Mark Out on Timeline or Clip and\n"
"additional Mark In on Timeline or Clip to perform this edit."
msgstr ""
#: trackaction.py:83
msgid "Not enough vertical space on Timeline to expand track"
msgstr ""
#: trackaction.py:84
msgid ""
"Maximize or resize application window to get more\n"
"space for tracks if possible."
msgstr ""
#: medialinker.py:107
msgid "Load Project For Relinking"
msgstr ""
#: medialinker.py:115
msgid "Original Media Missing:"
msgstr ""
#: medialinker.py:116
msgid "Original Media Found:"
msgstr ""
#: medialinker.py:119
msgid "Project:"
msgstr ""
#: medialinker.py:120
msgid ""
msgstr ""
#: medialinker.py:143
msgid "Display Missing Media Files"
msgstr ""
#: medialinker.py:144
msgid "Display Found Media Files"
msgstr ""
#: medialinker.py:155
msgid "Save Relinked Project As..."
msgstr ""
#: medialinker.py:272
msgid "Missing Media File Path"
msgstr ""
#: medialinker.py:273
msgid "Found Media File Path"
msgstr ""
#: medialinker.py:277
msgid "Media File Re-link Path"
msgstr ""
#: medialinker.py:438
msgid "Select Media File To Relink To"
msgstr ""
#: medialinker.py:478
msgid "Original path: "
msgstr ""
#: medialinker.py:481
msgid "Relink path: "
msgstr ""
#: medialinker.py:490
msgid "Media Asset Paths"
msgstr ""
#: medialinker.py:527
msgid "Relinked version of the Project saved!"
msgstr ""
#: medialinker.py:528
msgid ""
"To test the project, close this tool and open the relinked version in "
"Flowblade."
msgstr ""
#: patternproducer.py:352
msgid "Create Color Clip"
msgstr ""
#: patternproducer.py:358
msgid "Color Clip"
msgstr ""
#: patternproducer.py:367
msgid "Select Color:"
msgstr ""
#: patternproducer.py:385
msgid "Create Ising Clip"
msgstr ""
#: patternproducer.py:394
msgid "Noise temperature:"
msgstr ""
#: patternproducer.py:395
msgid "Border growth:"
msgstr ""
#: patternproducer.py:396
msgid "Spontanious growth:"
msgstr ""
#: patternproducer.py:416
msgid "Create Color Pulse Clip"
msgstr ""
#: patternproducer.py:428
msgid "Speed 1:"
msgstr ""
#: patternproducer.py:429
msgid "Speed 2:"
msgstr ""
#: patternproducer.py:430
msgid "Speed 3:"
msgstr ""
#: patternproducer.py:431
msgid "Speed 4:"
msgstr ""
#: patternproducer.py:432
msgid "Move 1:"
msgstr ""
#: patternproducer.py:433
msgid "Move 2:"
msgstr ""
#: tools/gmic.py:133
msgid "G'Mic not found!"
msgstr ""
#: tools/gmic.py:134
msgid ""
"G'Mic binary was not present at /usr/bin/gmic.\n"
"Install G'MIC to use this tool."
msgstr ""
#: tools/gmic.py:307
msgid "Select Video Media"
msgstr ""
#: tools/gmic.py:382 tools/gmic.py:783
msgid "not set"
msgstr ""
#: tools/gmic.py:400
msgid "Save Gmic Script As"
msgstr ""
#: tools/gmic.py:424
msgid "Load Gmic Script"
msgstr ""
#: tools/gmic.py:552
msgid "Video Encoding Settings"
msgstr ""
#: tools/gmic.py:556
msgid "Set Encoding"
msgstr ""
#: tools/gmic.py:601 tools/gmic.py:935
msgid "Load Clip"
msgstr ""
#: tools/gmic.py:605
msgid "no clip loaded"
msgstr ""
#: tools/gmic.py:630
msgid "no preview"
msgstr ""
#: tools/gmic.py:677
msgid "Preview"
msgstr ""
#: tools/gmic.py:704
msgid "Add to Script"
msgstr ""
#: tools/gmic.py:764
msgid "Frames Folder:"
msgstr ""
#: tools/gmic.py:775
msgid "Encode Video"
msgstr ""
#: tools/gmic.py:780
msgid "Encoding settings"
msgstr ""
#: tools/gmic.py:799
msgid "Set Mark In, Mark Out and Frames Folder for valid render"
msgstr ""
#: tools/gmic.py:842
msgid "Load Script"
msgstr ""
#: tools/gmic.py:844
msgid "Save Script"
msgstr ""
#: tools/gmic.py:903
msgid "frames"
msgstr ""
#: tools/gmic.py:921
msgid " no video file"
msgstr ""
#: tools/gmic.py:923
msgid " render video file"
msgstr ""
#: tools/gmic.py:924
msgid " frame(s),"
msgstr ""
#: tools/gmic.py:936
msgid "G'Mic Webpage"
msgstr ""
#: tools/gmic.py:1110
msgid "Rendering preview..."
msgstr ""
#: tools/gmic.py:1143
msgid "Preview for frame: "
msgstr ""
#: tools/gmic.py:1144
msgid ", render time: "
msgstr ""
#: tools/gmic.py:1212
msgid "Waiting for frames write to complete..."
msgstr ""
#: tools/gmic.py:1225
msgid "Rendering frame: "
msgstr ""
#: tools/gmic.py:1255
msgid "Render error!"
msgstr ""
#: tools/gmic.py:1297
msgid "Rendering video, "
msgstr ""
#: tools/gmic.py:1297
#, python-format
msgid "% done"
msgstr ""
#: tools/gmic.py:1307
msgid "Render complete!"
msgstr ""
#: tools/gmic.py:1317
msgid "Writing clip frame: "
msgstr ""
#: tools/gmic.py:1329
msgid "Render stopped!"
msgstr ""
#: tools/toolsencoding.py:90
msgid "Use Default Profile:"
msgstr ""
#: monitorevent.py:316
msgid "On some systems Trim View may update slowly"
msgstr ""
#: monitorevent.py:317
msgid ""
"Trim View works best with SSDs and relatively powerful processors.\n"
"\n"
msgstr ""
#: monitorevent.py:318
msgid ""
"Select 'Trim View Off' or'Trim View Single Side Edits Only' "
"options\n"
"if performance is not satisfactory."
msgstr ""
#: compositorfades.py:256
msgid "Clip too short!"
msgstr ""
#: compositorfades.py:257
msgid "The Clip is too short to add the requested fade."
msgstr ""
#: compositorfades.py:262
msgid "Clip too short for Auto Fades!"
msgstr ""
#: compositorfades.py:263
msgid ""
"The Clip is too short to add the user set default fades on Compositor "
"creation."
msgstr ""
#: audiosync.py:142
msgid "Cannot Timeline Audio Sync with Compound Clips!"
msgstr ""
#: audiosync.py:143
msgid "Audio syncing for Compound Clips is not supported."
msgstr ""
#: audiosync.py:184
msgid "Audio Sync parent clips must be on differnt tracks "
msgstr ""
#: audiosync.py:185
msgid ""
"Selected audio sync clip is on the sametrack as the sync action origin clip."
msgstr ""
#: audiosync.py:222
msgid "Audio sync move not possible"
msgstr ""
#: audiosync.py:223
msgid "Clip starts "
msgstr ""
#: audiosync.py:223
msgid ""
" frames before timeline start if it is moved \n"
"to be in audio sync with the specified clip.\n"
"\n"
msgstr ""
#: audiosync.py:224
msgid ""
"You need to move forward or shorten the clips in question to make the "
"operation succeed."
msgstr ""
#: audiosync.py:271
msgid "Cannot Create Audio Sync Compound Clip from Compound Clips!"
msgstr ""
#: audiosync.py:272
msgid "Audio syncing Compound Clips is not supported."
msgstr ""
#: audiosync.py:280
msgid "Cannot Create Audio Sync Compound Clip from 2 Audio Clips!"
msgstr ""
#: audiosync.py:281
msgid "One of the media items needs to be a video clip."
msgstr ""
#: audiosync.py:312
msgid "SYNC_CLIP_"
msgstr ""
#: audiosync.py:313
msgid "Save Sync Compound Clip XML"
msgstr ""
#: diskcachemanagement.py:47
msgid "Destroy data"
msgstr ""
#: diskcachemanagement.py:71
msgid ""
"Destroying this data may change contents of existing\n"
"projects and make some projects unopenable."
msgstr ""
#: diskcachemanagement.py:102
msgid " MB"
msgstr ""
#: diskcachemanagement.py:104
msgid " kB"
msgstr ""
#: diskcachemanagement.py:114
msgid "Confirm Destroying Cached Data!"
msgstr ""
#: diskcachemanagement.py:116
msgid ""
"Destroying this data may change contents of existing\n"
"projects or make some projects unopenable!"
msgstr ""
#: diskcachemanagement.py:118
msgid ""
"You can use 'File->Save Backup Snapshot...' functionality to backup "
"projects\n"
"so that they can be opened later before destroying this data."
msgstr ""
#: diskcachemanagement.py:120
msgid "Destroying this data may require parts of it to be recreated later."
msgstr ""
#: diskcachemanagement.py:172
msgid "Audio Levels Data"
msgstr ""
#: diskcachemanagement.py:173
msgid "G'Mic Tool Session Data"
msgstr ""
#: diskcachemanagement.py:174
msgid "Rendered Files"
msgstr ""
#: diskcachemanagement.py:175
msgid "Thumbnails"
msgstr ""
#: diskcachemanagement.py:176
msgid "User Created Custom Profiles"
msgstr ""
#: toolsintegration.py:116
msgid "Slow/Fast Motion"
msgstr ""
#: toolsintegration.py:129
msgid "Reverse"
msgstr ""
#: projectmediaimport.py:145
msgid "Loading Media Import Project"
msgstr ""
#: shortcuts.py:182
msgid "Set Mark In"
msgstr ""
#: shortcuts.py:183
msgid "Set Mark Out"
msgstr ""
#: shortcuts.py:184
msgid "Start / Stop Playback"
msgstr ""
#: shortcuts.py:185
msgid "Prev Edit/Mark"
msgstr ""
#: shortcuts.py:186
msgid "Next Edit/Mark"
msgstr ""
#: shortcuts.py:187
msgid "Prev Frame"
msgstr ""
#: shortcuts.py:191
msgid "Append Selected Media From Bin"
msgstr ""
#: shortcuts.py:192
msgid "Backwards Faster"
msgstr ""
#: shortcuts.py:194
msgid "Forward Faster"
msgstr ""
#: shortcuts.py:196
msgid "Resync selected Clip or Compositor"
msgstr ""
#: shortcuts.py:197
msgid "Delete Selected Item"
msgstr ""
#: shortcuts.py:198
msgid "Lift Selected Item"
msgstr ""
#: shortcuts.py:199
msgid "Go To Start"
msgstr ""
#: shortcuts.py:200
msgid "Go To End"
msgstr ""
#: shortcuts.py:201
msgid "3 Point Overwrite"
msgstr ""
#: shortcuts.py:203
msgid "Trim Tool Ripple Mode On/Off"
msgstr ""
#: shortcuts.py:204
msgid "Cut Active Tracks"
msgstr ""
#: shortcuts.py:205
msgid "Cut All Tracks"
msgstr ""
#: shortcuts.py:208 workflow.py:67
msgid "Trim"
msgstr ""
#: shortcuts.py:209 workflow.py:68
msgid "Roll"
msgstr ""
#: shortcuts.py:210 workflow.py:69
msgid "Slip"
msgstr ""
#: shortcuts.py:211 workflow.py:70
msgid "Spacer"
msgstr ""
#: shortcuts.py:215
msgid "Switch Monitor Source"
msgstr ""
#: shortcuts.py:216
msgid "Add Mark"
msgstr ""
#: shortcuts.py:217
msgid "Complete Keyboard Trim Edit"
msgstr ""
#: shortcuts.py:218
msgid "Nudge Move Selection Back 1 Frame"
msgstr ""
#: shortcuts.py:219
msgid "Nudge Move Selection Forward 1 Frame"
msgstr ""
#: shortcuts.py:220
msgid "Nudge Move Selection Back 10 Frames"
msgstr ""
#: shortcuts.py:221
msgid "Nudge Move Selection Forward 10 Frames"
msgstr ""
#: shortcuts.py:222
msgid "Open Next Media Item In Monitor"
msgstr ""
#: shortcuts.py:231
msgid "SPACE"
msgstr ""
#: shortcuts.py:234
msgid "Left Arrow"
msgstr ""
#: shortcuts.py:245
msgid "HOME"
msgstr ""
#: shortcuts.py:246
msgid "END"
msgstr ""
#: shortcuts.py:251
msgid "Key Pad END"
msgstr ""
#: shortcuts.py:252
msgid "Key Pad 1"
msgstr ""
#: shortcuts.py:254 shortcuts.py:257 shortcuts.py:258
msgid "Key Pad 2"
msgstr ""
#: shortcuts.py:255
msgid "Key Pad Down Arrow"
msgstr ""
#: shortcuts.py:260
msgid "Key Pad 4"
msgstr ""
#: shortcuts.py:261
msgid "Key Pad Left Arrow"
msgstr ""
#: shortcuts.py:263
msgid "Key Pad 5"
msgstr ""
#: shortcuts.py:264
msgid "Key Pad Begin"
msgstr ""
#: shortcuts.py:266
msgid "Key Pad 6"
msgstr ""
#: shortcuts.py:267
msgid "Key Pad Right Arrow"
msgstr ""
#: shortcuts.py:269
msgid "Key Pad 7"
msgstr ""
#: shortcuts.py:270
msgid "Key Pad HOME"
msgstr ""
#: shortcuts.py:273
msgid "TAB"
msgstr ""
#: shortcuts.py:275
msgid "ENTER"
msgstr ""
#: shortcuts.py:277
msgid "="
msgstr ""
#: shortcuts.py:278
msgid ","
msgstr ""
#: shortcuts.py:279
msgid "."
msgstr ""
#: shortcuts.py:281
msgid "Alt"
msgstr ""
#: shortcuts.py:283
msgid "Alt + Shift"
msgstr ""
#: shortcuts.py:284
msgid "Control"
msgstr ""
#: clipmenuaction.py:387
msgid "Previous clip does not have enough material to cover blank area"
msgstr ""
#: clipmenuaction.py:388 clipmenuaction.py:417
msgid "Requested edit can't be done."
msgstr ""
#: clipmenuaction.py:416
msgid "Next clip does not have enough material to cover blank area"
msgstr ""
#: dialogutils.py:91
msgid "Confirm"
msgstr ""
#: dialogutils.py:217
msgid "Can't edit a locked track"
msgstr ""
#: dialogutils.py:218
msgid " is locked. Unlock track to edit it."
msgstr ""
#: trimmodes.py:462
msgid "Can't use Trim tool on blank clips."
msgstr ""
#: trimmodes.py:463
msgid "You can use Move or Roll tools instead."
msgstr ""
#: trimmodes.py:1012
msgid "Initializing Roll tool failed"
msgstr ""
#: trimmodes.py:1013
msgid ""
"You are attempting a roll trim at a position in the timeline\n"
"where it can't be performed."
msgstr ""
#: menuactions.py:122
msgid "Help page not found!"
msgstr ""
#: menuactions.py:122
msgid ""
"Unfortunately the webresource containing help information\n"
"for this application was not found."
msgstr ""
#: exporting.py:378
msgid "Sequence is too short"
msgstr ""
#: exporting.py:378
msgid "Sequence needs to be at least 2 frames long to allow frame export."
msgstr ""
#: exporting.py:437
msgid "Export Image"
msgstr ""
#: exporting.py:438
msgid "Export Frame Image"
msgstr ""
#: exporting.py:462
msgid "Export file name:"
msgstr ""
#: exporting.py:464
msgid "Select target folder"
msgstr ""
#: exporting.py:468
msgid "Export folder:"
msgstr ""
#: exporting.py:475
msgid "Image type:"
msgstr ""
#: exporting.py:517
msgid ""
"When exporting audio to Ardour, the selected folder\n"
"has to be empty."
msgstr ""
#: workflow.py:66
msgid "Move"
msgstr ""
#: workflow.py:72
msgid "Ripple Trim"
msgstr ""
#: workflow.py:73
msgid "Cut"
msgstr ""
#: workflow.py:74
msgid "Keyframe"
msgstr ""
#: workflow.py:75
msgid "Multitrim"
msgstr ""
#: workflow.py:78
msgid ""
"Left Mouse to move and insert single clip between clips.\n"
"CTRL + Left Mouse to select and move clip range.\n"
"\n"
"Left Mouse on clip ends to trim clip length."
msgstr ""
#: workflow.py:79
msgid ""
"Left Mouse to move clip into new position.\n"
"CTRL + Left Mouse to select and move clip range into new position.\n"
"\n"
"Left Mouse on clip ends to trim clip length."
msgstr ""
#: workflow.py:80
msgid ""
"Left Mouse to trim closest clip end.\n"
"Left or Right Arrow Key + Enter Key to do the edit using "
"keyboard."
msgstr ""
#: workflow.py:81
msgid ""
"Left Mouse to move closest edit point between 2 clips.\n"
"Left or Right Arrow Key + Enter Key to do the edit using "
"keyboard."
msgstr ""
#: workflow.py:82
msgid ""
"Left Mouse to move clip contents within clip.\n"
"Left or Right Arrow Key + Enter Key to do the edit using "
"keyboard."
msgstr ""
#: workflow.py:83
msgid ""
"Left Mouse to move clip under cursor and all clips after it forward "
"or backward, overwrites not allowed.\n"
"CTRL + Left Mouse to move clip under cursor and all clips after it "
"on the same track forward or backward, overwrites not allowed."
msgstr ""
#: workflow.py:84
msgid ""
"1. Left Mouse to draw a box to select a group of clips.\n"
"2. Left Mouse inside the box to move selected clips forward or "
"backward."
msgstr ""
#: workflow.py:85
msgid ""
"Left Mouse to trim closest clip end and move all clips after it to "
"maintain sync, overwrites not allowed.\n"
"Left or Right Arrow Key + Enter Key to do the edit using "
"keyboard."
msgstr ""
#: workflow.py:86
msgid ""
"Left Mouse to cut clip under cursor.\n"
"CTRL + Left Mouse to cut clips on all tracks at cursor position."
msgstr ""
#: workflow.py:87
msgid ""
"Click Left Mouse on Clip to init Volume Keyframe editing, Brightness "
"for media with no audio data.\n"
"Left Mouse to create or drag keyframes.\n"
"Delete Key to delete active Keyframe."
msgstr ""
#: workflow.py:88
msgid ""
"Position cursor near or on clip edges for Trim and Roll "
"edits.\n"
"Position cursor on clip center for Slip edit.\n"
"Drag with Left Mouse to do edits.\n"
"\n"
"Enter Key to start keyboard edit, Left or Right Arrow Key to "
"move edit point.\n"
"Enter Key to complete keyboard edit."
msgstr ""
#: workflow.py:91
msgid ""
"\n"
"\n"
"Left Mouse Drag to draw a box to select a group of clips and move\n"
"the selected clips forward or backward."
msgstr ""
#: workflow.py:173
msgid "Workflow Presets"
msgstr ""
#: workflow.py:178
msgid "Standard"
msgstr ""
#: workflow.py:182
msgid "Film Style"
msgstr ""
#: workflow.py:192
msgid "Behaviours"
msgstr ""
#: workflow.py:198
msgid "Default Delete Action"
msgstr ""
#: workflow.py:208
msgid "Drag'n'Drop Action"
msgstr ""
#: workflow.py:212
msgid "Always Overwrite Blanks"
msgstr ""
#: workflow.py:212
msgid "Overwrite Blanks on non-V1 Tracks"
msgstr ""
#: workflow.py:212
msgid "Always Insert"
msgstr ""
#: workflow.py:220
msgid "New Sequence Default Compositing Mode"
msgstr ""
#: workflow.py:233
msgid "Show Tooltips for Tools"
msgstr ""
#: workflow.py:321
msgid "Tool Active"
msgstr ""
#: workflow.py:329
msgid "Set Position"
msgstr ""
#: workflow.py:349
msgid "Do Box Selection and Box Move from empty press"
msgstr ""
#: workflow.py:425
msgid "Workflow First Run Wizard"
msgstr ""
#: workflow.py:427
msgid "Select Preset Workflow and Continue"
msgstr ""
#: workflow.py:431
msgid "Welcome to Flowblade 2.4"
msgstr ""
#: workflow.py:436
msgid "Flowblade 2.4 comes with a configurable workflow."
msgstr ""
#: workflow.py:441
msgid " You can select which tools you want to use.\n"
msgstr ""
#: workflow.py:442
msgid " Many timeline edit behaviours are configurable.\n"
msgstr ""
#: workflow.py:447
msgid "Select Workflow Preset"
msgstr ""
#: workflow.py:452
msgid ""
"You can change and configure individual tools and behaviours anytime"
msgstr ""
#: workflow.py:456
msgid " by pressing "
msgstr ""
#: workflow.py:462
msgid " icon."
msgstr ""
#: workflow.py:465
msgid "Standard"
msgstr ""
#: workflow.py:466
msgid ""
"Standard workflow has the Move tool as default tool\n"
"and presents a workflow\n"
"similar to most video editors."
msgstr ""
#: workflow.py:469
msgid "Film Style"
msgstr ""
#: workflow.py:470
msgid ""
"Film Style workflow has the Insert tool as default tool\n"
"and employs insert style editing.\n"
"This was the workflow in previous versions of the application."
msgstr ""
#: workflow.py:473
msgid "Keep Existing Worflow"
msgstr ""
#: workflow.py:474
msgid ""
"Select this if you have installed new version and wish to keep your existing "
"workflow."
msgstr ""
#: kftoolmode.py:408
msgid "Media Frame: "
msgstr ""
#: kftoolmode.py:1373
msgid "Delete all but last Keyframe after Clip Range"
msgstr ""
#: kftoolmode.py:1385
msgid "Edit Volume Keyframes"
msgstr ""
#: kftoolmode.py:1390
msgid "Edit Brightness Keyframes"
msgstr ""
#: kftoolmode.py:1396
msgid "Edit Other Filter Parameters"
msgstr ""
#: kftoolmode.py:1438
msgid "Leading Keyframes"
msgstr ""
#: kftoolmode.py:1445
msgid "Trailing Keyframes"
msgstr ""
#: kftoolmode.py:1457
msgid "Playhead Follows Dragged Keyframe"
msgstr ""
#: kftoolmode.py:1463
msgid "Value Snapping"
msgstr ""
#: kftoolmode.py:1497
msgid "Exit Edit"
msgstr ""
#: tools/rotomask.py:96
msgid "RotoMaskEditor"
msgstr ""
#: tools/rotomask.py:149
msgid "Mask Type:"
msgstr ""
#: tools/rotomask.py:151
msgid "Curve Mask"
msgstr ""
#: tools/rotomask.py:152
msgid "Line Mask"
msgstr ""
#: tools/rotomask.py:159
msgid "Allow to add / delete points in closed masks"
msgstr ""
#: tools/rotomask.py:161
msgid "Close Tool"
msgstr ""
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/locale/add_language 0000775 0000000 0000000 00000001740 13610327166 0026665 0 ustar 00root root 0000000 0000000 #!/bin/bash
# Get language
LANG=$1
echo "Adding Flowblade translation files for ISO 639-1 language code: $1"
# Move to Flowblade root directory
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd $SCRIPT_DIR
cd ..
ROOT_DIR=$(pwd)
# Check if directory for translation already exists
TRANS_FILE=$ROOT_DIR"/locale/"$LANG"/LC_MESSAGES/flowblade.po"
if [ -f $TRANS_FILE ]; then
echo "Translation files for $LANG_NAME already exist."
echo "No new translation files were created."
exit 1
fi
# Create directory for translation files
NEW_DIR=$ROOT_DIR"/locale/"$LANG
NEW_DIR_TWO=$NEW_DIR"/LC_MESSAGES"
mkdir $NEW_DIR
mkdir $NEW_DIR_TWO
# Create .pot file
./locale/create_pot
# Create language .po file
SOURCE_POT=$ROOT_DIR"/locale/Flowblade/flowblade.pot"
msginit --output-file=$TRANS_FILE --input=$SOURCE_POT --locale=$LANG
# Give info
echo "New translation file for $LANG was created."
echo "Edit it and run 'compile_language $LANG' to compile binary file and test it."
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/locale/compile_language 0000775 0000000 0000000 00000001134 13610327166 0027562 0 ustar 00root root 0000000 0000000 #!/bin/bash
# Get language
LANG=$1
echo "Compiling .mo file for ISO 639-1 language code: $LANG"
# Move to Flowblade root directory
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd $SCRIPT_DIR
cd ..
ROOT_DIR=$(pwd)
# Check if directory for translation already exists
PO_FILE=$ROOT_DIR"/locale/"$LANG"/LC_MESSAGES/flowblade.po"
if [ ! -f $PO_FILE ]; then
echo "Translation file $PO_FILE does not exist."
echo "No .mo files were compiled."
exit 1
fi
# Create mo file
MO_FILE=$ROOT_DIR"/locale/"$LANG"/LC_MESSAGES/flowblade.mo"
msgfmt --output-file=$MO_FILE $PO_FILE
echo "Done."
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/locale/copy_to_usr 0000775 0000000 0000000 00000000773 13610327166 0026644 0 ustar 00root root 0000000 0000000 #!/bin/bash
# Move to Flowblade root directory
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd $SCRIPT_DIR
cd ..
ROOT_DIR=$(pwd)
echo $SCRIPT_DIR
echo $ROOT_DIR
LANGUAGES=("fi" "cs" "fr" "es" "it" "de")
echo "Copying all compiled translation files languages to /usr/share/locale"
for LANG in "${LANGUAGES[@]}"
do
:
LANG_FILE=$SCRIPT_DIR"/"$LANG"/LC_MESSAGES/flowblade.mo"
COPY_FILE="/usr/share/locale/"$LANG"/LC_MESSAGES/flowblade.mo"
sudo cp $LANG_FILE $COPY_FILE
done
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/locale/create_pot 0000775 0000000 0000000 00000002100 13610327166 0026406 0 ustar 00root root 0000000 0000000 #!/bin/bash
# Move to Flowblade root directory
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd $SCRIPT_DIR
cd ..
ROOT_DIR=$(pwd)
# Creates .pot file that can be turned to .po file for each language
xgettext -o locale/Flowblade/flowblade.pot app.py projectaction.py editorwindow.py clipeffectseditor.py compositeeditor.py dialogs.py editevent.py editorpersistance.py guicomponents.py movemodes.py panels.py persistance.py projectdata.py render.py syncsplitevent.py translations.py mlttransitions.py propertyeditorbuilder.py keyframeeditor.py middlebar.py medialog.py projectinfogui.py tools/titler.py rendergui.py profilesmanager.py preferenceswindow.py tools/batchrendering.py proxyediting.py tlineaction.py extraeditors.py trackaction.py medialinker.py patternproducer.py tools/gmic.py tools/gmic.py tools/toolsencoding.py monitorevent.py compositorfades.py audiosync.py diskcachemanagement.py toolsintegration.py projectmediaimport.py shortcuts.py clipmenuaction.py dialogutils.py trimmodes.py menuactions.py exporting.py workflow.py kftoolmode.py tools/rotomask.py
flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/locale/cs/ 0000775 0000000 0000000 00000000000 13610327166 0024747 5 ustar 00root root 0000000 0000000 flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/locale/cs/LC_MESSAGES/ 0000775 0000000 0000000 00000000000 13610327166 0026534 5 ustar 00root root 0000000 0000000 flowblade-2.4.0.1-fix_release/flowblade-trunk/Flowblade/locale/cs/LC_MESSAGES/flowblade.mo 0000664 0000000 0000000 00000402225 13610327166 0031035 0 ustar 00root root 0000000 0000000 9 r 0 ! R A n ?
J &