pax_global_header 0000666 0000000 0000000 00000000064 13062777160 0014523 g ustar 00root root 0000000 0000000 52 comment=20894070faaf6c7a74bcf5970f70dcacc2a09c94
flowblade-1.12/ 0000775 0000000 0000000 00000000000 13062777160 0013405 5 ustar 00root root 0000000 0000000 flowblade-1.12/.gitignore 0000664 0000000 0000000 00000000562 13062777160 0015400 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-1.12/README.md 0000664 0000000 0000000 00000007014 13062777160 0014666 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 and Contact](https://github.com/jliljebl/flowblade#forum-webpage-and-contact)
# Introduction
Flowblade is a **multitrack non-linear video editor** for Linux released under **GPL 3 license**.
Flowblade is designed to provide a fast, precise and robust editing experience. Flowblade employs a film-style insert editing model as workflow. In insert editing clips are generally placed tightly after other clips when they are inserted on the timeline. Edits are fine tuned by trimming in and out points of clips or by cutting and deleting parts of clips.
Flowblade provides powerful tools to mix and filter video and audio.
# Features
**Editing:**
* 3 move tools
* 3 trim tools
* 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:**
* 6 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 1.10 was released on December 2016.
**Next release:** Flowblade Movie Editor 1.12 will be out on March 2017.
# 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 1.4 dark theme](./flowblade-trunk/docs/Screenshot-1-4-dark.png)
[Screenshot 0.18 light theme](./flowblade-trunk/docs/Screenshot-0-18.png)
These are in the repository */docs* folder.
# Forum, Webpage and Contact
[The project webpage is here](http://jliljebl.github.io/flowblade/).
For questions and discussion on Flowblade we have a [Google+ group] (https://plus.google.com/u/0/communities/103860400113389238474) available. There will be some updates on what is happening with the project too.
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-1.12/flowblade-trunk/ 0000775 0000000 0000000 00000000000 13062777160 0016505 5 ustar 00root root 0000000 0000000 flowblade-1.12/flowblade-trunk/AUTHORS 0000664 0000000 0000000 00000000104 13062777160 0017550 0 ustar 00root root 0000000 0000000 FLOWBLADE
Application programmed and designed by:
Janne Liljeblad
flowblade-1.12/flowblade-trunk/COPYING 0000664 0000000 0000000 00000101045 13062777160 0017541 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-1.12/flowblade-trunk/Flowblade/ 0000775 0000000 0000000 00000000000 13062777160 0020404 5 ustar 00root root 0000000 0000000 flowblade-1.12/flowblade-trunk/Flowblade/__init__.py 0000664 0000000 0000000 00000000036 13062777160 0022514 0 ustar 00root root 0000000 0000000 #
# This file marks module.
#
flowblade-1.12/flowblade-trunk/Flowblade/app.py 0000664 0000000 0000000 00000071712 13062777160 0021546 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 md5
import mlt
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 medialog
import mlt
import mltenv
import mltfilters
import mltplayer
import mltprofiles
import mlttransitions
import movemodes
import persistance
import positionbar
import preferenceswindow
import projectaction
import projectdata
import projectinfogui
import proxyediting
import render
import renderconsumer
import respaths
import resync
import sequence
import snapping
import titler
import tlinewidgets
import toolsintegration
import toolnatron
import trimmodes
import translations
import undo
import updater
import utils
import jackaudio
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
loaded_autosave_file = None
splash_screen = None
splash_timeout_id = -1
exit_timeout_id = -1
window_resize_id = -1
window_state_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 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"
# passing -xdg as a flag will change the user_dir location with XDG_CONFIG_HOME
# For full xdg-app support all the launch processes need to add this too, currently not impl.
for arg in sys.argv:
if arg.lower() == "-xdg":
editorstate.use_xdg = True
# Create hidden folders if not present
user_dir = utils.get_hidden_user_dir_path()
print "User dir:",user_dir
if not os.path.exists(user_dir):
os.mkdir(user_dir)
if not os.path.exists(user_dir + mltprofiles.USER_PROFILES_DIR):
os.mkdir(user_dir + mltprofiles.USER_PROFILES_DIR)
if not os.path.exists(user_dir + AUTOSAVE_DIR):
os.mkdir(user_dir + AUTOSAVE_DIR)
if not os.path.exists(user_dir + BATCH_DIR):
os.mkdir(user_dir + BATCH_DIR)
if not os.path.exists(user_dir + appconsts.AUDIO_LEVELS_DIR):
os.mkdir(user_dir + appconsts.AUDIO_LEVELS_DIR)
if not os.path.exists(utils.get_hidden_screenshot_dir_path()):
os.mkdir(utils.get_hidden_screenshot_dir_path())
if not os.path.exists(user_dir + appconsts.GMIC_DIR):
os.mkdir(user_dir + appconsts.GMIC_DIR)
if not os.path.exists(user_dir + appconsts.MATCH_FRAME_DIR):
os.mkdir(user_dir + appconsts.MATCH_FRAME_DIR)
if not os.path.exists(user_dir + appconsts.TRIM_VIEW_DIR):
os.mkdir(user_dir + appconsts.TRIM_VIEW_DIR)
if not os.path.exists(user_dir + appconsts.NATRON_DIR):
os.mkdir(user_dir + appconsts.NATRON_DIR)
# Set paths.
respaths.set_paths(root_path)
# Load editor prefs and list of recent projects
editorpersistance.load()
if editorpersistance.prefs.dark_theme == True:
respaths.apply_dark_theme()
if editorpersistance.prefs.display_all_audio_levels == False:
editorstate.display_all_audio_levels = False
editorpersistance.create_thumbs_folder_if_needed(user_dir)
editorpersistance.create_rendered_clips_folder_if_needed(user_dir)
editorpersistance.save()
# Init translations module with translations data
translations.init_languages()
translations.load_filters_translations()
mlttransitions.init_module()
# RHEL7/CentOS compatibility fix
if gtk_version == "3.8.8":
GObject.threads_init()
# Init gtk threads
Gdk.threads_init()
Gdk.threads_enter()
# Request dark theme if so desired
if editorpersistance.prefs.dark_theme == True:
Gtk.Settings.get_default().set_property("gtk-application-prefer-dark-theme", True)
# Load drag'n'drop images
dnd.init()
# Adjust gui parameters for smaller screens
scr_w = Gdk.Screen.width()
scr_h = Gdk.Screen.height()
editorstate.SCREEN_WIDTH = scr_w
editorstate.SCREEN_HEIGHT = scr_h
print scr_w, scr_h
print "Small height:", editorstate.screen_size_small_height()
print "Small width:", editorstate.screen_size_small_width()
_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()
# 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()
toolnatron.init()
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()
# Clear splash
if ((editorpersistance.prefs.display_splash_screen == True) and len(autosave_files) == 0):
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()
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)
# Launch gtk+ main loop
Gtk.main()
Gdk.threads_leave()
# ----------------------------------- callback setting
def monkeypatch_callbacks():
# Prefences setting
preferenceswindow.select_thumbnail_dir_callback = projectaction.select_thumbnail_dir_callback
preferenceswindow.select_render_clips_dir_callback = projectaction.select_render_clips_dir_callback
# 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(editevent.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
# These provide clues for further module refactoring
# ---------------------------------- 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()
updater.set_clip_edit_mode_callback = editevent.set_clip_monitor_edit_mode
updater.load_icons()
# Notebook indexes are differn 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)
# Set button to display sequence in toggled state.
gui.sequence_editor_b.set_active(True)
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")
# 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 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.clip_editor_b.set_sensitive(False)
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()
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
gui.editor_window.modes_selector.set_pixbuf(0)
editevent.insert_move_mode_pressed()
# Create array needed to update compositors after all edits
editorstate.current_sequence().restack_compositors()
proxyediting.set_menu_to_proxy_state()
# 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()
updater.window_resized()
gui.editor_window.window.handler_unblock(window_resize_id)
gui.editor_window.window.handler_unblock(window_state_id)
start_autosave()
if new_project.update_media_lengths_on_load == True:
projectaction.update_media_lengths()
gui.editor_window.handle_insert_move_mode_button_press()
editorstate.trim_mode_ripple = False
#editorstate.project.c_seq.print_all()
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))
start_autosave()
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 = utils.get_hidden_user_dir_path() + 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 = utils.get_hidden_user_dir_path() + 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 = "_" + md5.new(str(os.urandom(32))).hexdigest()
def get_instance_autosave_file():
return AUTOSAVE_FILE + instance_autosave_id_str
def start_autosave():
global autosave_timeout_id
time_min = 1 # hard coded, probably no need to make configurable
autosave_delay_millis = time_min * 60 * 1000
print "Autosave started..."
autosave_timeout_id = GObject.timeout_add(autosave_delay_millis, do_autosave)
autosave_file = utils.get_hidden_user_dir_path() + get_instance_autosave_file()
persistance.save_project(editorstate.PROJECT(), autosave_file)
def get_autosave_files():
autosave_dir = utils.get_hidden_user_dir_path() + 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 = utils.get_hidden_user_dir_path() + 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)
# ------------------------------------------------------- small screens
def _set_draw_params():
if editorstate.screen_size_small_width() == True:
appconsts.NOTEBOOK_WIDTH = 450
editorwindow.MONITOR_AREA_WIDTH = 450
editorwindow.MEDIA_MANAGER_WIDTH = 220
if editorstate.screen_size_small_height() == True:
appconsts.TOP_ROW_HEIGHT = 10
projectinfogui.PROJECT_INFO_PANEL_HEIGHT = 140
if editorstate.SCREEN_WIDTH < 1153 and editorstate.SCREEN_HEIGHT < 865:
editorwindow.MONITOR_AREA_WIDTH = 400
positionbar.BAR_WIDTH = 100
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..."
# 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()
editorpersistance.prefs.mm_paned_position = gui.editor_window.mm_paned.get_position()
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(utils.get_hidden_user_dir_path() + get_instance_autosave_file())
except:
print "Delete autosave file FAILED"
# Exit gtk main loop.
Gtk.main_quit()
flowblade-1.12/flowblade-trunk/Flowblade/appconsts.py 0000664 0000000 0000000 00000011302 13062777160 0022765 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
# Mediaview 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
# 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.
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 as it is no longer meaningful
# Notebook widths
NOTEBOOK_WIDTH = 600 # defines app min width 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 max for flowblade
MAX_TRACKS = 9
# 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 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/"
AUDIO_LEVELS_DIR = "audiolevels/"
# Hidden media folders
THUMBNAILS_DIR = "thumbnails"
RENDERED_CLIPS_DIR = "rendered_clips"
GMIC_DIR = "gmic"
NODE_COMPOSITORS_DIR = "node_compositors"
PHANTOM_DISK_CACHE_DIR = "phantom_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"
NATRON_DIR = "natron"
# 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
flowblade-1.12/flowblade-trunk/Flowblade/audiomonitoring.py 0000664 0000000 0000000 00000052131 13062777160 0024167 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 > 898:
METER_SLOT_H = 400
CONTROL_SLOT_H = 240
METER_LIGHTS = 123
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 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 onlty detached when this called from wondow 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 _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
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
cr.set_source_rgb(*METER_BG_COLOR)
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 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 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, u"\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)
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)
class MasterVolumeMeter:
def __init__(self):
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.top_pad = 14
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, False, False, 0)
def _draw(self, event, cr, allocation):
x, y, w, h = allocation
cr.set_source_rgb(*METER_BG_COLOR)
cr.rectangle(0, 0, w, h)
cr.fill()
grad = cairo.LinearGradient (0, self.top_pad, 0, METER_HEIGHT + self.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)
l_value, r_value = _audio_levels[0]
x = 0
self.meter.display_value(cr, x, l_value, r_value, grad)
flowblade-1.12/flowblade-trunk/Flowblade/audiowaveform.py 0000664 0000000 0000000 00000016770 13062777160 0023641 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 math
import md5
import mlt
import os
import pickle
import struct
import threading
import time
from gi.repository import Gtk, Gdk
import appconsts
import dialogutils
from editorstate import PROJECT
import gui
import guiutils
import updater
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
return
cache_file_path = utils.get_hidden_user_dir_path() + appconsts.AUDIO_LEVELS_DIR + _get_unique_name_for_media(clip.path)
if os.path.isfile(cache_file_path):
f = open(cache_file_path)
frame_levels = pickle.load(f)
frames_cache[clip.path] = frame_levels
clip.waveform_data = frame_levels
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 = utils.get_hidden_user_dir_path() + 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
write_file = file(self.file_cache_path, "wb")
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.encode('utf-8'), 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").encode('utf-8'), 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-1.12/flowblade-trunk/Flowblade/audiowaveformrenderer.py 0000664 0000000 0000000 00000020561 13062777160 0025361 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 math
import md5
import mlt
import os
import pickle
import struct
import subprocess
import sys
import threading
from gi.repository import Gtk, Gdk
import appconsts
import editorpersistance
import editorstate
import mltenv
import mltprofiles
import mlttransitions
import mltfilters
import renderconsumer
import respaths
import translations
import updater
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):
f = open(levels_file_path)
waveform = pickle.load(f)
_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 utils.get_hidden_user_dir_path() + 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(utils.get_hidden_user_dir_path() + "log_audio_levels_render", 'w')
process = subprocess.Popen([sys.executable, respaths.LAUNCH_DIR + "flowbladeaudiorender", \
self.rendered_media, self.profile_desc, respaths.ROOT_PATH], \
stdin=FLOG, stdout=FLOG, stderr=FLOG)
process.wait()
Gdk.threads_enter()
updater.repaint_tline()
Gdk.threads_leave()
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
return
cache_file_path = utils.get_hidden_user_dir_path() + appconsts.AUDIO_LEVELS_DIR + _get_unique_name_for_media(clip.path)
if os.path.isfile(cache_file_path):
f = open(cache_file_path)
frame_levels = pickle.load(f)
frames_cache[clip.path] = frame_levels
clip.waveform_data = frame_levels
return
global waveform_thread
waveform_thread = WaveformCreator(clip, track.height, dialog)
waveform_thread.start()
# --------------------------------------------------------- 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"
# 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()
# 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
write_file = file(self.file_cache_path, "wb")
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-1.12/flowblade-trunk/Flowblade/boxmove.py 0000664 0000000 0000000 00000025347 13062777160 0022450 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 Overwrite Box tool functionality.
"""
import edit
import editorstate
from editorstate import current_sequence
import tlinewidgets
import updater
box_selection_data = None
edit_data = None
def clear_data():
# these need to cleared when box move 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 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 to move
if box_selection_data.is_hit(event.x, event.y) == False:
# Back to start state
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 to select
edit_data["mouse_point"] = (x, y)
else: # mouse move 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
if edit_data == None:
return
if box_selection_data == None: # mouse action to select
box_selection_data = BoxMoveData(edit_data["press_point"], (x, y))
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}
else: # mouse action to move
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
tlinewidgets.set_edit_mode_data(edit_data)
updater.repaint_tline()
class BoxMoveData:
"""
This class collects and data needed for boxovewrite moves.
"""
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
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
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-1.12/flowblade-trunk/Flowblade/cairoarea.py 0000664 0000000 0000000 00000011542 13062777160 0022707 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 CairoDrawableArea widget. You can draw onto it using
Cairo, and listen to its mouse and keyboard events.
"""
from gi.repository import Gtk
from gi.repository import GObject
from gi.repository import Gdk
import gui
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
# Flag for grabbing focus
self.set_property("can-focus", True)
self.grab_focus_on_press = True
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. Can be removed.
# 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-1.12/flowblade-trunk/Flowblade/clipeffectseditor.py 0000664 0000000 0000000 00000051432 13062777160 0024461 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
"""
from gi.repository import GLib
from gi.repository import Gtk
import time
import dnd
import edit
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
# 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):
"""
Use components created at clipeffectseditor.py.
"""
create_widgets()
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)
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)
exit_button_vbox.pack_start(Gtk.Label(), True, True, 0)
info_row = Gtk.HBox(False, 2)
info_row.pack_start(widgets.clip_info, False, False, 0)
info_row.pack_start(exit_button_vbox, True, True, 0)
combo_row = Gtk.HBox(False, 2)
combo_row.pack_start(group_combo_box, True, True, 0)
combo_row.pack_start(guiutils.get_pad_label(8, 2), False, False, 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)
effects_vbox.pack_start(info_row, False, False, 0)
if editorstate.screen_size_small_height() == False:
effects_vbox.pack_start(guiutils.get_pad_label(2, 2), 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
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):
"""
Sets clip being edited and inits gui.
"""
global clip, track, clip_index
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()
effect_selection_changed() # This may get called twice
gui.middle_notebook.set_current_page(filters_notebook_index) # 2 == index of clipeditor page in notebook
def clip_removed_during_edit(removed_clip):
"""
Called from edit.py after a clip is removed from timeline during edit
so that we cannot edit effects on clip that is no longer on timeline.
"""
if clip == removed_clip:
clear_clip()
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 == "moveup":
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 == "movedown":
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()
clear_effects_edit_panel()
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.
"""
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(_("Add"))
widgets.del_effect_b = Gtk.Button(_("Delete"))
widgets.toggle_all = Gtk.Button()
widgets.toggle_all.set_image(Gtk.Image.new_from_file(respaths.IMAGE_PATH + "filters_all_toggle.png"))
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())
# 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)
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()
filter_info = get_selected_filter_info()
def get_filter_add_action(filter_info, target_clip):
if filter_info.multipart_filter == False:
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 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
treeselection = widgets.effect_stack_view.treeview.get_selection()
(model, rows) = treeselection.get_selected_rows()
try:
row = rows[0]
except:
return # This fails when there are filters but no rows are selected
row_index = max(row)
data = {"clip":clip,
"index":row_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:
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 effect_selection_changed():
global keyframe_editor_widgets
# Check we have clip
if clip == None:
keyframe_editor_widgets = []
return
# Check we actually have filters so we can display one.
# If not, clear previous filters from view.
if len(clip.filters) == 0:
vbox = Gtk.VBox(False, 0)
vbox.pack_start(Gtk.Label(), False, False, 0)
widgets.value_edit_frame.remove(widgets.value_edit_box)
widgets.value_edit_frame.add(vbox)
vbox.show_all()
widgets.value_edit_box = vbox
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
filter_object = clip.filters[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)):
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(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 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_positions():
if clip == None:
return
for kf_widget in keyframe_editor_widgets:
kf_widget.update_clip_pos()
flowblade-1.12/flowblade-trunk/Flowblade/clipenddragmode.py 0000664 0000000 0000000 00000015256 13062777160 0024110 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 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)
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)
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-1.12/flowblade-trunk/Flowblade/clipmenuaction.py 0000664 0000000 0000000 00000040104 13062777160 0023767 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
import os
import shutil
import time
import audiowaveform
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
import movemodes
import syncsplitevent
import tlinewidgets
import tlineaction
import updater
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 editevent.track_lock_check_and_user_info(track, display_clip_menu, "clip context menu"):
return False
# Display popup
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)
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
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
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}
action = edit.add_compositor_action(edit_data)
action.do_edit()
updater.repaint_tline()
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 stting clip unmuted
data = {"clip":clip}
action = edit.unmute_clip(data)
action.do_edit()
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):
clip, track, item_id, item_data = data
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]
# 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):
clip, track, item_id, item_data = data
clip_index = movemodes.selected_range_out + 1
blank_index = movemodes.selected_range_in
if clip_index < 0: # we're not getting legal clip index
return
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 = utils.get_hidden_user_dir_path() + 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 = utils.get_hidden_user_dir_path() + 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()
# 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}
flowblade-1.12/flowblade-trunk/Flowblade/compositeeditor.py 0000664 0000000 0000000 00000021154 13062777160 0024172 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 clips compositing gui.
"""
import copy
from gi.repository import Gtk
import gui
import guicomponents
import guiutils
import edit
from editorstate import current_sequence
import editorpersistance
import keyframeeditor
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.
"""
# Left side
widgets.compositor_info = guicomponents.CompositorInfoPanel()
widgets.delete_b = Gtk.Button(_("Delete"))
widgets.delete_b.connect("clicked", lambda w,e: _delete_compositor_pressed(), None)
widgets.reset_b = Gtk.Button(_("Reset"))
widgets.reset_b.connect("clicked", lambda w,e: _reset_compositor_pressed(), None)
# Right side
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()
compositor_vbox = Gtk.VBox(False, 2)
compositor_vbox.pack_start(widgets.compositor_info, False, False, 0)
compositor_vbox.pack_start(Gtk.Label(), True, True, 0)
compositor_vbox.pack_start(widgets.reset_b, False, False, 0)
compositor_vbox.pack_start(widgets.delete_b, False, False, 0)
compositor_vbox.pack_start(guiutils.get_pad_label(5, 3), False, False, 0)
set_enabled(False)
return compositor_vbox
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.delete_b.set_sensitive(value)
widgets.reset_b.set_sensitive(value)
def maybe_clear_editor(killed_compositor):
if killed_compositor.destroy_id == compositor.destroy_id:
clear_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"))
vbox.pack_start(widgets.empty_label, True, True, 0)
vbox.pack_start(Gtk.Label(), True, True, 0)
vbox.show_all()
widgets.value_edit_box = vbox
widgets.value_edit_frame.add(vbox)
return
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
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.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 editor based on editor type, not based on EditableProperty type as above
# because one editor set 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_positions():
for kf_widget in keyframe_editor_widgets:
kf_widget.update_clip_pos()
flowblade-1.12/flowblade-trunk/Flowblade/compositormodes.py 0000664 0000000 0000000 00000013645 13062777160 0024215 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 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,
"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,
"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 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):
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()
global sub_mode
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-1.12/flowblade-trunk/Flowblade/dialogs.py 0000664 0000000 0000000 00000175376 13062777160 0022423 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 utils
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").encode('utf-8'), Gtk.ResponseType.REJECT,
_("OK").encode('utf-8'), 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(5, 4)
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 _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").encode('utf-8'), Gtk.ResponseType.REJECT,
_("Save With Changed Profile").encode('utf-8'), 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").encode('utf-8'), Gtk.ResponseType.REJECT,
_("Save With Changed Profile").encode('utf-8'), 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").encode('utf-8'), Gtk.ResponseType.REJECT,
_("OK").encode('utf-8'), 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 load_project_dialog(callback, parent=None):
if parent == None:
parent = gui.editor_window.window
dialog = Gtk.FileChooserDialog(_("Select Project File"), parent,
Gtk.FileChooserAction.OPEN,
(_("Cancel").encode('utf-8'), Gtk.ResponseType.CANCEL,
_("OK").encode('utf-8'), 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").encode('utf-8'), Gtk.ResponseType.CANCEL,
_("Save").encode('utf-8'), 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 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").encode('utf-8'), Gtk.ResponseType.CANCEL,
_("Export").encode('utf-8'), 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 save_env_data_dialog(callback):
dialog = Gtk.FileChooserDialog(_("Save Runtime Environment Data"), gui.editor_window.window,
Gtk.FileChooserAction.SAVE,
(_("Cancel").encode('utf-8'), Gtk.ResponseType.CANCEL,
_("Save").encode('utf-8'), 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 select_thumbnail_dir(callback, parent_window, current_dir_path, retry_open_media):
panel, file_select = panels.get_thumbnail_select_panel(current_dir_path)
cancel_str = _("Cancel").encode('utf-8')
ok_str = _("Ok").encode('utf-8')
dialog = Gtk.Dialog(_("Select Thumbnail Folder"),
parent_window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(cancel_str, Gtk.ResponseType.CANCEL,
ok_str, Gtk.ResponseType.YES))
dialog.vbox.pack_start(panel, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
_default_behaviour(dialog)
dialog.connect('response', callback, (file_select, retry_open_media))
dialog.show_all()
def select_rendred_clips_dir(callback, parent_window, current_dir_path, context_data=None):
panel, file_select = panels.get_render_folder_select_panel(current_dir_path)
cancel_str = _("Cancel").encode('utf-8')
ok_str = _("Ok").encode('utf-8')
dialog = Gtk.Dialog(_("Select Thumbnail Folder"),
parent_window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(cancel_str, Gtk.ResponseType.CANCEL,
ok_str, Gtk.ResponseType.YES))
dialog.vbox.pack_start(panel, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
_default_behaviour(dialog)
if context_data == None:
dialog.connect('response', callback, file_select)
else:
dialog.connect('response', callback, file_select, context_data)
dialog.show_all()
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").encode('utf-8'), Gtk.ResponseType.CLOSE,
_("Cancel").encode('utf-8'), Gtk.ResponseType.CANCEL,
_("Save").encode('utf-8'), 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").encode('utf-8'), Gtk.ResponseType.CLOSE,
_("Cancel").encode('utf-8'), Gtk.ResponseType.CANCEL,
_("Save").encode('utf-8'), 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").encode('utf-8'), Gtk.ResponseType.ACCEPT))
img = Gtk.Image.new_from_file(respaths.IMAGE_PATH + "flowbladeappicon.png")
flow_label = Gtk.Label(label="Flowblade Movie Editor")
ver_label = Gtk.Label(label="1.12.0")
janne_label = Gtk.Label(label="Copyright 2016 Janne Liljeblad and contributors")
page_label = Gtk.Label(label="Project page: https://github.com/jliljebl/flowblade")
flow_label.modify_font(Pango.FontDescription("sans bold 14"))
janne_label.modify_font(Pango.FontDescription("sans 8"))
page_label.modify_font(Pango.FontDescription("sans 8"))
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)
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)
license_view = guicomponents.get_gpl3_scroll_widget((450, 370))
alignment3 = dialogutils.get_default_alignment(license_view)
alignment3.set_size_request(450, 370)
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)
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_view = guicomponents.get_translations_scroll_widget((450, 370))
alignment4 = dialogutils.get_default_alignment(translations_view)
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").encode('utf-8'), 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")
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").encode('utf-8'), 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").encode('utf-8'), 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 _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").encode('utf-8'), Gtk.ResponseType.CANCEL,
_("Force Delete").encode('utf-8'), 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").encode('utf-8'), Gtk.ResponseType.CANCEL,
_("Open Autosaved Project").encode('utf-8'), 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").encode('utf-8'), Gtk.ResponseType.CANCEL,
_("Open Selected Autosave").encode('utf-8'), 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):
dialog = Gtk.Dialog(_("Change Sequence Tracks Count"), gui.editor_window.window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Cancel").encode('utf-8'), Gtk.ResponseType.REJECT,
_("Change Tracks").encode('utf-8'), Gtk.ResponseType.ACCEPT))
tracks_select = guicomponents.TracksNumbersSelect(5, 4)
info_text = _("Please note:\n") + \
u"\u2022" + _(" It is recommended that you save Project before completing this operation\n") + \
u"\u2022" + _(" There is no Undo for this operation\n") + \
u"\u2022" + _(" Current Undo Stack will be destroyed\n") + \
u"\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 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").encode('utf-8'), Gtk.ResponseType.REJECT,
_("Create Sequence").encode('utf-8'), 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(5, 4)
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").encode('utf-8'), Gtk.ResponseType.REJECT,
_("Rename").encode('utf-8'), 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").encode('utf-8'), Gtk.ResponseType.REJECT,
_("Rename").encode('utf-8'), 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").encode('utf-8'), Gtk.ResponseType.REJECT,
_("Create").encode('utf-8'), 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").encode('utf-8'),
_("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").encode('utf-8'), 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 open_image_sequence_dialog(callback, parent_window):
cancel_str = _("Cancel").encode('utf-8')
ok_str = _("Ok").encode('utf-8')
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").encode('utf-8'), Gtk.ResponseType.CANCEL,
_("Export").encode('utf-8'), 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").encode('utf-8'), gui.editor_window.window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Cancel").encode('utf-8'), Gtk.ResponseType.REJECT,
_("Apply").encode('utf-8'), 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 fade_edit_dialog(callback, transition_data):
dialog = Gtk.Dialog(_("Add Fade"), gui.editor_window.window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Cancel").encode('utf-8'), Gtk.ResponseType.REJECT,
_("Apply").encode('utf-8'), 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):
dialog = Gtk.Dialog(_("Keyboard Shortcuts"),
parent_window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Close").encode('utf-8'), Gtk.ResponseType.CLOSE))
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_kb_row(_("DELETE"), _("Delete Selected Item")), 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_kb_row(_("TAB"), _("Switch Monitor Source")), False, False, 0)
general_vbox.pack_start(_get_kb_row(_("Control + L"), _("Log Marked Clip Range")), False, False, 0)
general = guiutils.get_named_frame(_("General"), general_vbox)
tline_vbox = Gtk.VBox()
tline_vbox.pack_start(_get_kb_row("I", _("Set Mark In")), False, False, 0)
tline_vbox.pack_start(_get_kb_row("O", _("Set 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_kb_row("X", _("Cut Clip")), False, False, 0)
tline_vbox.pack_start(_get_kb_row(_("DELETE"), _("Splice Out")), False, False, 0)
tline_vbox.pack_start(_get_kb_row("Y", _("Insert")), False, False, 0)
tline_vbox.pack_start(_get_kb_row("U", _("Append")), False, False, 0)
tline_vbox.pack_start(_get_kb_row("T", _("3 Point Overwrite Insert")), False, False, 0)
tline_vbox.pack_start(_get_kb_row("M", _("Add Mark")), 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_kb_row(_("R"), _("Trim Tool Ripple Mode On/Off")), False, False, 0)
tline_vbox.pack_start(_get_kb_row(_("S"), _("Resync selected Clip or Compositor")), False, False, 0)
tline_vbox.pack_start(_get_kb_row(_("G"), _("Log Marked Clip 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_kb_row(_("ENTER"), _("Complete Keyboard Trim Edit")), False, False, 0)
tline = guiutils.get_named_frame(_("Timeline"), tline_vbox)
play_vbox = Gtk.VBox()
play_vbox.pack_start(_get_kb_row(_("SPACE"), _("Start / Stop Playback")), False, False, 0)
play_vbox.pack_start(_get_kb_row("J", _("Backwards Faster")), False, False, 0)
play_vbox.pack_start(_get_kb_row("K", _("Stop")), False, False, 0)
play_vbox.pack_start(_get_kb_row("L", _("Forward Faster")), False, False, 0)
play_vbox.pack_start(_get_kb_row(_("Left Arrow "), _("Prev Frame")), False, False, 0)
play_vbox.pack_start(_get_kb_row(_("Right Arrow"), _("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_kb_row(_("Up Arrow"), _("Next Edit/Mark")), False, False, 0)
play_vbox.pack_start(_get_kb_row(_("Down Arrow"), _("Prev Edit/Mark")), False, False, 0)
play_vbox.pack_start(_get_kb_row(_("HOME"), _("Go To Start")), False, False, 0)
play_vbox.pack_start(_get_kb_row(_("END"), _("Go 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()
tools_vbox.pack_start(_get_kb_row("1", _("Insert")), False, False, 0)
tools_vbox.pack_start(_get_kb_row("2", _("Overwrite")), False, False, 0)
tools_vbox.pack_start(_get_kb_row("3", _("Trim")), False, False, 0)
tools_vbox.pack_start(_get_kb_row("4", _("Roll")), False, False, 0)
tools_vbox.pack_start(_get_kb_row("5", _("Slip")), False, False, 0)
tools_vbox.pack_start(_get_kb_row("6", _("Spacer")), False, False, 0)
tools_vbox.pack_start(_get_kb_row("7", _("Box")), False, False, 0)
tools_vbox.pack_start(_get_kb_row(_("R"), _("Trim Tool Ripple Mode On/Off")), False, False, 0)
tools = guiutils.get_named_frame(_("Tools"), tools_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"), _("Snap to X or Y of drag start point")), False, False, 0)
geom = guiutils.get_named_frame(_("Geometry Editor"), geom_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(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(geom, False, False, 0)
pad_panel = Gtk.HBox()
pad_panel.pack_start(guiutils.pad_label(12,12), False, False, 0)
pad_panel.pack_start(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)
guiutils.set_margins(sw, 24, 24, 24, 24)
dialog.vbox.pack_start(sw, True, True, 0)
dialogutils.set_outer_margins(dialog.vbox)
_default_behaviour(dialog)
dialog.connect('response', _dialog_destroy)
dialog.show_all()
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)
return row
def watermark_dialog(add_callback, remove_callback):
dialog = Gtk.Dialog(_("Sequence Watermark"), gui.editor_window.window,
Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Close").encode('utf-8'), 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").encode('utf-8'), Gtk.ResponseType.CANCEL,
_("OK").encode('utf-8'), 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").encode('utf-8'), Gtk.ResponseType.REJECT,
_("Change To File Profile").encode('utf-8'), 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()
flowblade-1.12/flowblade-trunk/Flowblade/dialogutils.py 0000664 0000000 0000000 00000016551 13062777160 0023306 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 guiutils
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").encode('utf-8'), 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").encode('utf-8'), 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").encode('utf-8'), 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):
content = get_warning_message_dialog_panel(primary_txt, secondary_txt, is_info)
align = get_default_alignment(content)
dialog = Gtk.Dialog("",
parent_window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Cancel").encode('utf-8'), Gtk.ResponseType.REJECT,
_("OK").encode('utf-8'), 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").encode('utf-8'), 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()
flowblade-1.12/flowblade-trunk/Flowblade/dnd.py 0000664 0000000 0000000 00000021730 13062777160 0021526 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)
#EFFECTS_STACK_DND_TARGET = Gtk.TargetEntry.new('effectstack', 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 used to hold data needed on drag drop instead of the API provided 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)
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-1.12/flowblade-trunk/Flowblade/edit.py 0000664 0000000 0000000 00000300521 13062777160 0021704 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 audiowaveform
import appconsts
import compositeeditor
from editorstate import current_sequence
from editorstate import get_track
from editorstate import PLAYER
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)
updater.clip_removed_during_edit(clip)
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)
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)
# 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
def undo(self):
PLAYER().stop_playback()
# HACK, see above.
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()
# HACK, see above.
if self.stop_for_edit:
PLAYER().consumer.start()
if do_gui_update:
self._update_gui()
def redo(self):
PLAYER().stop_playback()
# HACK, see above.
if self.stop_for_edit:
PLAYER().consumer.stop()
movemodes.clear_selected_clips() # selection not valid after 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()
tlinewidgets.set_match_frame(-1, -1, True)
# HACK, see above.
if self.stop_for_edit:
PLAYER().consumer.start()
if do_gui_update:
self._update_gui()
def _update_gui(self):
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:
updater.update_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()
# ---------------------------------------------------- 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)
#----------------- 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
_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):
print self.__dict__
_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:
#print "MULTI_TRIM_REMOVE for track", track.id, "values: ", self.edit_delta, applied_delta, -self.multi_data.max_backwards
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)
#print "MULTI_TRIM_REMOVE for track", track.id, "values: ", applied_delta, -self.multi_data.max_backwards
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
action.update_hidden_track_blank = 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)
# ----------------------------------- 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"
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()
# Hack!!! Some filters don't seem to handle setting compositors None (and the
# following gc) and crash, so we'll hold references to them forever.
#global old_compositors
#old_compositors.append(self.compositor)
compositeeditor.maybe_clear_editor(self.compositor)
self.compositor = None
def _add_compositor_redo(self):
self.compositor = current_sequence().create_compositor(self.compositor_type)
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 succesive 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()
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()
# Hack!!! Some filters don't seem to handle setting compositors None (and the
# following gc) and crash, so we'll hold references to them forever.
#global old_compositors
#old_compositors.append(self.compositor)
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)
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)
#----------------- 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
#------------------- 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)
# -------------------------------------------------------- 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-1.12/flowblade-trunk/Flowblade/editevent.py 0000664 0000000 0000000 00000102274 13062777160 0022753 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
import time
from gi.repository import Gtk
from gi.repository import Gdk
import appconsts
import boxmove
import clipeffectseditor
import clipenddragmode
import compositeeditor
import compositormodes
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 medialog
import movemodes
import multimovemode
import syncsplitevent
import tlinewidgets
import trimmodes
import undo
import updater
import utils
# module state
mouse_disabled = False # Used to ignore drag and release events when press doesn't start an action that can handle those events.
repeat_event = None
parent_selection_data = None # Held here until user presses tline again
# 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 ((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):
dialogutils.warning_message(_("Can't put an audio clip on a video track."),
_("Track ")+ utils.get_track_name(track, current_sequence()) + _(" is a video track and can't display audio only material."),
gui.editor_window.window)
# ------------------------------------- edit mode setting
def set_default_edit_mode(disable_mouse=False):
"""
This is used as global 'go to start position' exit door from
situations where for example user is in trim and exits it
without specifying which edit mode to go to.
NOTE: As this uses 'programmed click', this method does nothing if insert mode button
is already down.
"""
gui.editor_window.handle_insert_move_mode_button_press()
gui.editor_window.set_mode_selector_to_mode()
if disable_mouse:
global mouse_disabled
mouse_disabled = True
def set_clip_monitor_edit_mode():
"""
Going to clip monitor exits active trimodes into non active trimmodes.
"""
if EDIT_MODE() == editorstate.ONE_ROLL_TRIM:
oneroll_trim_no_edit_init()
elif EDIT_MODE() == editorstate.ONE_ROLL_TRIM_NO_EDIT:
pass
elif EDIT_MODE() == editorstate.TWO_ROLL_TRIM:
tworoll_trim_no_edit_init()
elif EDIT_MODE() == editorstate.TWO_ROLL_TRIM_NO_EDIT:
pass
else:
gui.editor_window.handle_insert_move_mode_button_press()
gui.editor_window.set_mode_selector_to_mode()
def set_post_undo_redo_edit_mode():
if EDIT_MODE() == editorstate.ONE_ROLL_TRIM:
oneroll_trim_no_edit_init()
if EDIT_MODE() == editorstate.TWO_ROLL_TRIM:
tworoll_trim_no_edit_init()
def stop_looping():
# Stop trim mode looping using trimmodes.py methods for it
# Called when entering move modes.
if PLAYER().looping():
if EDIT_MODE() == editorstate.ONE_ROLL_TRIM:
trimmodes.oneroll_stop_pressed()
if EDIT_MODE() == editorstate.TWO_ROLL_TRIM:
trimmodes.tworoll_stop_pressed()
# -------------------------------------------------------------- move modes
def insert_move_mode_pressed():
"""
User selects Insert tool.
"""
stop_looping()
current_sequence().clear_hidden_track()
editorstate.edit_mode = editorstate.INSERT_MOVE
tlinewidgets.set_edit_mode(None, tlinewidgets.draw_insert_overlay)
_set_move_mode()
def overwrite_move_mode_pressed():
"""
User selects Overwrite tool.
"""
stop_looping()
current_sequence().clear_hidden_track()
editorstate.edit_mode = editorstate.OVERWRITE_MOVE
# Box tool is implemeted as sub mode of OVERWRITE_MOVE so this false
editorstate.overwrite_mode_box = False
tlinewidgets.set_edit_mode(None, tlinewidgets.draw_overwrite_overlay)
_set_move_mode()
def box_mode_pressed():
"""
User selects Box tool.
"""
stop_looping()
current_sequence().clear_hidden_track()
# Box tool is implemeted as sub mode of OVERWRITE_MOVE
editorstate.edit_mode = editorstate.OVERWRITE_MOVE
editorstate.overwrite_mode_box = True
boxmove.clear_data()
tlinewidgets.set_edit_mode(None, None) # these get set later for box move
_set_move_mode()
def multi_mode_pressed():
"""
User selects Spacer tool.
"""
stop_looping()
current_sequence().clear_hidden_track()
editorstate.edit_mode = editorstate.MULTI_MOVE
tlinewidgets.set_edit_mode(None, tlinewidgets.draw_multi_overlay)
updater.set_move_mode_gui()
updater.repaint_tline()
def _set_move_mode():
updater.set_move_mode_gui()
updater.set_transition_render_edit_menu_items_sensitive(movemodes.selected_range_in, movemodes.selected_range_out)
updater.repaint_tline()
# -------------------------------------------------------------- one roll trim
def oneroll_trim_no_edit_init():
"""
This mode is entered and this method is called when:
- user first selects trim tool
- user does cut(X) action while in trim mode
- user clicks empty and preference is to keep using trim tool (to not exit to INSERT_MOVE)
"""
stop_looping()
editorstate.edit_mode = editorstate.ONE_ROLL_TRIM_NO_EDIT
gui.editor_window.set_cursor_to_mode()
tlinewidgets.set_edit_mode(None, None) # No overlays are drawn in this edit mode
movemodes.clear_selected_clips() # Entering trim edit mode clears selection
updater.set_trim_mode_gui()
def oneroll_trim_no_edit_press(event, frame):
"""
Mouse press while in ONE_ROLL_TRIM_NO_EDIT attempts to init edit and
move to ONE_ROLL_TRIM mode.
"""
success = oneroll_trim_mode_init(event.x, event.y)
if success:
# If not quick enter, disable edit until mouse released
if not editorpersistance.prefs.quick_enter_trims:
global mouse_disabled
tlinewidgets.trim_mode_in_non_active_state = True
mouse_disabled = True
# If preference is quick enter, call mouse move handler immediately
# to move edit point to where mouse is
else:
trimmodes.oneroll_trim_move(event.x, event.y, frame, None)
else:
if editorpersistance.prefs.empty_click_exits_trims == True:
set_default_edit_mode(True)
else:
editorstate.edit_mode = editorstate.ONE_ROLL_TRIM_NO_EDIT
def oneroll_trim_no_edit_move(x, y, frame, state):
# Only presses are handled in ONE_ROLL_TRIM_NO_EDIT mode
pass
def oneroll_trim_no_edit_release(x, y, frame, state):
# Only presses are handled in ONE_ROLL_TRIM_NO_EDIT mode
pass
def oneroll_trim_mode_init(x, y):
"""
User enters ONE_ROLL_TRIM mode from ONE_ROLL_TRIM_NO_EDIT
"""
track = tlinewidgets.get_track(y)
if track == None:
return False
if track_lock_check_and_user_info(track, oneroll_trim_mode_init, "one roll trim mode"):
set_default_edit_mode()
return False
stop_looping()
editorstate.edit_mode = editorstate.ONE_ROLL_TRIM
movemodes.clear_selected_clips() # Entering trim edit mode clears selection
updater.set_trim_mode_gui()
# init mode
press_frame = tlinewidgets.get_frame(x)
trimmodes.set_exit_mode_func = set_default_edit_mode
trimmodes.set_no_edit_mode_func = oneroll_trim_no_edit_init
success = trimmodes.set_oneroll_mode(track, press_frame)
return success
# --------------------------------------------------------- two roll trim
def tworoll_trim_no_edit_init():
stop_looping()
editorstate.edit_mode = editorstate.TWO_ROLL_TRIM_NO_EDIT
gui.editor_window.set_cursor_to_mode()
tlinewidgets.set_edit_mode(None, None) # No overlays are drawn in this edit mode
movemodes.clear_selected_clips() # Entering trim edit mode clears selection
updater.set_trim_mode_gui()
def tworoll_trim_no_edit_press(event, frame):
success = tworoll_trim_mode_init(event.x, event.y)
if success:
if not editorpersistance.prefs.quick_enter_trims:
global mouse_disabled
tlinewidgets.trim_mode_in_non_active_state = True
mouse_disabled = True
else:
trimmodes.tworoll_trim_move(event.x, event.y, frame, None)
else:
if editorpersistance.prefs.empty_click_exits_trims == True:
set_default_edit_mode(True)
else:
editorstate.edit_mode = editorstate.TWO_ROLL_TRIM_NO_EDIT
def tworoll_trim_no_edit_move(x, y, frame, state):
pass
def tworoll_trim_no_edit_release(x, y, frame, state):
pass
def tworoll_trim_mode_init(x, y):
"""
User selects two roll mode
"""
track = tlinewidgets.get_track(y)
if track == None:
return False
if track_lock_check_and_user_info(track, tworoll_trim_mode_init, "two roll trim mode",):
set_default_edit_mode()
return False
stop_looping()
editorstate.edit_mode = editorstate.TWO_ROLL_TRIM
movemodes.clear_selected_clips() # Entering trim edit mode clears selection
updater.set_trim_mode_gui()
press_frame = tlinewidgets.get_frame(x)
trimmodes.set_exit_mode_func = set_default_edit_mode
trimmodes.set_no_edit_mode_func = tworoll_trim_no_edit_init
success = trimmodes.set_tworoll_mode(track, press_frame)
return success
# ----------------------------------------------------- slide trim
def slide_trim_no_edit_init():
stop_looping() # Stops looping
editorstate.edit_mode = editorstate.SLIDE_TRIM_NO_EDIT
gui.editor_window.set_cursor_to_mode()
tlinewidgets.set_edit_mode(None, None) # No overlays are drawn in this edit mode
movemodes.clear_selected_clips() # Entering trim edit mode clears selection
updater.set_trim_mode_gui()
def slide_trim_no_edit_press(event, frame):
success = slide_trim_mode_init(event.x, event.y)
if success:
if not editorpersistance.prefs.quick_enter_trims:
global mouse_disabled
tlinewidgets.trim_mode_in_non_active_state = True
mouse_disabled = True
else:
trimmodes.edit_data["press_start"] = frame
trimmodes.slide_trim_move(event.x, event.y, frame, None)
else:
if editorpersistance.prefs.empty_click_exits_trims == True:
set_default_edit_mode(True)
else:
editorstate.edit_mode = editorstate.SLIDE_TRIM_NO_EDIT
def slide_trim_no_edit_move(x, y, frame, state):
pass
def slide_trim_no_edit_release(x, y, frame, state):
pass
def slide_trim_mode_init(x, y):
"""
User selects two roll mode
"""
track = tlinewidgets.get_track(y)
if track == None:
return False
if track_lock_check_and_user_info(track, tworoll_trim_mode_init, "two roll trim mode"):
set_default_edit_mode()
return False
stop_looping()
editorstate.edit_mode = editorstate.SLIDE_TRIM
movemodes.clear_selected_clips() # Entering trim edit mode clears selection
updater.set_trim_mode_gui()
press_frame = tlinewidgets.get_frame(x)
trimmodes.set_exit_mode_func = set_default_edit_mode
trimmodes.set_no_edit_mode_func = slide_trim_no_edit_init
success = trimmodes.set_slide_mode(track, press_frame)
return success
# ------------------------------------ timeline mouse events
def tline_canvas_mouse_pressed(event, frame):
"""
Mouse event callback from timeline canvas widget
"""
global mouse_disabled
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)
mouse_disabled = True
# Set INSERT_MODE
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):
mouse_disabled == True
# 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:
editorstate.edit_mode = editorstate.INSERT_MOVE
# 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.y, current_sequence().compositors)
if hit_compositor != None:
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)
elif event.button == 3:
mouse_disabled == True
compositormodes.set_compositor_selected(hit_compositor)
guicomponents.display_compositor_popup_menu(event, hit_compositor,
compositor_menu_item_activated)
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)):
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):
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:
# 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 + CTRL: Select new trimmed clip in one roll trim mode
elif (event.button == 1
and (event.get_state() & Gdk.ModifierType.CONTROL_MASK)
and EDIT_MODE() == editorstate.ONE_ROLL_TRIM):
track = tlinewidgets.get_track(event.y)
if track == None:
if editorpersistance.prefs.empty_click_exits_trims == True:
set_default_edit_mode(True)
return
success = trimmodes.set_oneroll_mode(track, frame)
if (not success) and editorpersistance.prefs.empty_click_exits_trims == True:
set_default_edit_mode(True)
return
gui.editor_window.set_cursor_to_mode()
gui.editor_window.set_mode_selector_to_mode()
if not editorpersistance.prefs.quick_enter_trims:
mouse_disabled = True
else:
trimmodes.oneroll_trim_move(event.x, event.y, frame, None)
# LEFT BUTTON + CTRL: Select new trimmed clip in two roll trim mode
elif (event.button == 1
and (event.get_state() & Gdk.ModifierType.CONTROL_MASK)
and EDIT_MODE() == editorstate.TWO_ROLL_TRIM):
track = tlinewidgets.get_track(event.y)
if track == None:
if editorpersistance.prefs.empty_click_exits_trims == True:
set_default_edit_mode(True)
return
success = trimmodes.set_tworoll_mode(track, frame)
if (not success) and editorpersistance.prefs.empty_click_exits_trims == True:
set_default_edit_mode(True)
return
if not editorpersistance.prefs.quick_enter_trims:
mouse_disabled = True
else:
trimmodes.tworoll_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 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:
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()
global mouse_disabled
if 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()
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:
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()
set_default_edit_mode()
return
hit_compositor = tlinewidgets.compositor_hit(frame, 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]
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 track_lock_check_and_user_info(track):
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 track_lock_check_and_user_info(track):
set_default_edit_mode()
return
set_default_edit_mode()
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)
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:
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
if editorpersistance.prefs.overwrite_clip_drop == True:
if track.id != current_sequence().first_video_track().id:
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 track_lock_check_and_user_info(track):
set_default_edit_mode()
return
frame = tlinewidgets.get_frame(x)
clips = medialog.get_clips_for_rows(rows)
set_default_edit_mode()
do_multiple_clip_insert(track, clips, frame)
# ------------------------------------ track locks handling
def track_lock_check_and_user_info(track, calling_function="this ain't used anymore", actionname="this ain't used anymore"):
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.")
dialogutils.warning_message(primary_txt, secondary_txt, gui.editor_window.window)
return True
return False
# ------------------------------------ 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 = [oneroll_trim_no_edit_press,
oneroll_trim_no_edit_move,
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 = [tworoll_trim_no_edit_press,
tworoll_trim_no_edit_move,
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 = [slide_trim_no_edit_press,
slide_trim_no_edit_move,
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]
# (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}
flowblade-1.12/flowblade-trunk/Flowblade/editorpersistance.py 0000664 0000000 0000000 00000027143 13062777160 0024514 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.
"""
from gi.repository import Gtk
import os
import pickle
import appconsts
import mltprofiles
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
prefs = None
recent_projects = None
def load():
"""
If docs fail to load, new ones are created and saved.
"""
prefs_file_path = utils.get_hidden_user_dir_path() + PREFS_DOC
recents_file_path = utils.get_hidden_user_dir_path() + RECENT_DOC
global prefs, recent_projects
try:
f = open(prefs_file_path)
prefs = pickle.load(f)
except:
prefs = EditorPreferences()
write_file = file(prefs_file_path, "wb")
pickle.dump(prefs, write_file)
try:
f = open(recents_file_path)
recent_projects = pickle.load(f)
except:
recent_projects = utils.EmptyClass()
recent_projects.projects = []
write_file = file(recents_file_path, "wb")
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)
write_file = file(recents_file_path, "wb")
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
write_file = file(prefs_file_path, "wb")
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 = utils.get_hidden_user_dir_path() + PREFS_DOC
recents_file_path = utils.get_hidden_user_dir_path() + RECENT_DOC
write_file = file(prefs_file_path, "wb")
pickle.dump(prefs, write_file)
write_file = file(recents_file_path, "wb")
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 = utils.get_hidden_user_dir_path() + 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 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]
proj_name = proj_name.replace("_","__") # to display names with underscored correctly
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):
# Unpack widgets
gen_opts_widgets, edit_prefs_widgets, view_prefs_widgets, performance_widgets = widgets_tuples_tuple
default_profile_combo, open_in_last_opened_check, open_in_last_rendered_check, undo_max_spin, load_order_combo = gen_opts_widgets
# Jul-2016 - SvdB - Added play_pause_button
auto_play_in_clip_monitor_check, auto_center_check, grfx_insert_length_spin, \
trim_exit_click, trim_quick_enter, remember_clip_frame, overwrite_clip_drop, cover_delete, \
play_pause_button, mouse_scroll_action, hide_file_ext_button, auto_center_on_updown = edit_prefs_widgets
use_english, disp_splash, buttons_style, dark_theme, theme_combo, audio_levels_combo, window_mode_combo, full_names = 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_play_in_clip_monitor = auto_play_in_clip_monitor_check.get_active()
prefs.auto_center_on_play_stop = auto_center_check.get_active()
prefs.default_grfx_length = int(grfx_insert_length_spin.get_adjustment().get_value())
prefs.empty_click_exits_trims = trim_exit_click.get_active()
prefs.quick_enter_trims = trim_quick_enter.get_active()
prefs.remember_monitor_clip_frame = remember_clip_frame.get_active()
prefs.overwrite_clip_drop = (overwrite_clip_drop.get_active() == 0)
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.use_english_always = use_english.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.dark_theme = (dark_theme.get_active() == 1)
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()
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)
def create_thumbs_folder_if_needed(user_dir):
if prefs.thumbnail_folder == None:
thumbs_folder = user_dir + appconsts.THUMBNAILS_DIR
if not os.path.exists(thumbs_folder + "/"):
os.mkdir(thumbs_folder + "/")
prefs.thumbnail_folder = thumbs_folder
def create_rendered_clips_folder_if_needed(user_dir):
if prefs.render_folder == None:
render_folder = user_dir + appconsts.RENDERED_CLIPS_DIR
if not os.path.exists(render_folder + "/"):
os.mkdir(render_folder + "/")
prefs.render_folder = render_folder
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
self.auto_center_on_play_stop = False
self.thumbnail_folder = None
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 = 2
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
self.show_sequence_profile = True
self.buttons_style = GLASS_STYLE
self.dark_theme = False
self.remember_last_render_dir = True
self.empty_click_exits_trims = True
self.quick_enter_trims = True
self.show_vu_meter = True
self.remember_monitor_clip_frame = True
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
self.theme_fallback_colors = 0 # index of gui._THEME_COLORS
self.display_all_audio_levels = True
self.overwrite_clip_drop = True
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
flowblade-1.12/flowblade-trunk/Flowblade/editorstate.py 0000664 0000000 0000000 00000013074 13062777160 0023312 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
# Project being edited
project = None
# Wrapped MLT framework producer->consumer media player
player = None
# Current edit mode
edit_mode = INSERT_MOVE
# Trim tool ripple mode is expressed as a flag
trim_mode_ripple = False
# Ovewrite tool box mode is expressed as a 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
# 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
_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 = "0.10"
RUNNING_FROM_INSTALLATION = 0
RUNNING_FROM_DEV_VERSION = 1
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 pos
cursor_on_tline = False
# Flag for running JACK audio server. If this is on when SDLConsumer created in mltplayer.py
# jack rack filter will bw taached 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
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 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 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 < 1368:
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 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 = {}
flowblade-1.12/flowblade-trunk/Flowblade/editorwindow.py 0000664 0000000 0000000 00000156401 13062777160 0023503 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 GdkPixbuf
from gi.repository import Pango
import app
import appconsts
import audiomonitoring
import batchrendering
import boxmove
import clipeffectseditor
import clipmenuaction
import compositeeditor
import dialogs
import dnd
import editevent
import editorpersistance
import editorstate
import exporting
import glassbuttons
import gmic
import gui
import guicomponents
import guiutils
import medialinker
import medialog
import menuactions
import middlebar
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
# GUI size params
MEDIA_MANAGER_WIDTH = 250
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
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)
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
INSERTMOVE_CURSOR = cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "insertmove_cursor.png")
OVERWRITE_CURSOR = cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "overwrite_cursor.png")
OVERWRITE_BOX_CURSOR = cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "overwrite_cursor_box.png")
TWOROLL_CURSOR = cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "tworoll_cursor.png")
ONEROLL_CURSOR = cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "oneroll_cursor.png")
SLIDE_CURSOR = cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "slide_cursor.png")
ONEROLL_NO_EDIT_CURSOR = cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "oneroll_noedit_cursor.png")
TWOROLL_NO_EDIT_CURSOR = cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "tworoll_noedit_cursor.png")
SLIDE_NO_EDIT_CURSOR = cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "slide_noedit_cursor.png")
MULTIMOVE_CURSOR = cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "multimove_cursor.png")
MULTIMOVE_NO_EDIT_CURSOR = cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "multimove_cursor.png")
ONEROLL_RIPPLE_CURSOR = cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "oneroll_cursor_ripple.png")
ONEROLL_TOOL = cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "oneroll_tool.png")
OVERWRITE_TOOL = cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "overwrite_tool.png")
# 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()),
('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:tlineaction.do_timeline_objects_copy()),
('Paste', None, _('Paste'), 'V', None, lambda a:tlineaction.do_timeline_objects_paste()),
('PasteFilters', None, _('Paste Filters'), '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()),
('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'), None, None, lambda a:tlineaction.sync_all_compositors()),
('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()),
('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 Media Clip...'), 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()),
('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()),
('LogClipRange', None, _('Log Marked Clip Range'), 'L', None, lambda a:medialog.log_range_clicked()),
('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()),
('JackAudio', None, _("JACK Audio..."), None, None, lambda a: menuactions.jack_output_managing()),
('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()),
('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)),
('About', None, _('About'), None, None, lambda a:menuactions.about()),
('InsertMode', None, None, '1', None, lambda a:_this_is_not_used()),
('OverMode', None, None, '2', None, lambda a:_this_is_not_used()),
('OneRollMode', None, None, '3', None, lambda a:_this_is_not_used()),
('TwoRollMode', None, None, '4', None, lambda a:_this_is_not_used()),
('SlideMode', None, None, '5', None, lambda a:_this_is_not_used()),
('MultiMode', None, None, '6', None, lambda a:_this_is_not_used()),
('BoxMode', None, None, '7', None, lambda a:_this_is_not_used())
]
menu_string = """
"""
# 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)
# Menu box
menu_vbox = Gtk.VBox(False, 0)
menu_vbox.pack_start(self.menubar, False, True, 0)
# Media panel
self.bin_list_view = guicomponents.BinListView(
projectaction.bin_selection_changed,
projectaction.bin_name_edited)
dnd.connect_bin_tree_view(self.bin_list_view.treeview, projectaction.move_files_to_bin)
self.bin_list_view.set_property("can-focus", True)
bins_panel = panels.get_bins_panel(self.bin_list_view,
lambda w,e: projectaction.add_new_bin(),
lambda w,e: projectaction.delete_selected_bin())
bins_panel.set_size_request(MEDIA_MANAGER_WIDTH, 10) # this component is always expanded, so 10 for minimum size ok
bins_panel.set_margin_right(4)
self.media_list_view = guicomponents.MediaPanel(projectaction.media_file_menu_item_selected,
updater.set_and_display_monitor_media_file)
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 = 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,
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.mm_paned = Gtk.HPaned()
self.mm_paned.pack1(bins_panel, resize=True, shrink=True)
self.mm_paned.pack2(media_panel, resize=True, shrink=False)
mm_panel = guiutils.set_margins(self.mm_paned, 2, 2, 6, 2)
# 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 = 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)
effects_editor_panel = guiutils.set_margins(clipeffectseditor.widgets.value_edit_frame, 0, 0, 4, 0)
effects_hbox = Gtk.HBox()
effects_hbox.set_border_width(5)
effects_hbox.pack_start(clip_editor_panel, False, False, 0)
effects_hbox.pack_start(effects_editor_panel, True, True, 0)
self.effects_panel = guiutils.set_margins(effects_hbox, 2, 2, 2, 2)
# Compositors panel
compositor_clip_panel = 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.set_border_width(5)
compositors_hbox.pack_start(compositor_clip_panel, False, False, 0)
compositors_hbox.pack_start(compositor_editor_panel, True, True, 0)
self.compositors_panel = guiutils.set_margins(compositors_hbox, 2, 2, 2, 2)
# 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)
# 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)
media_log_panel = guiutils.set_margins(media_log_vbox, 6, 6, 6, 6)
self.media_log_events_list_view = media_log_events_list_view
# Sequence list
self.sequence_list_view = guicomponents.SequenceListView(
projectaction.sequence_name_edited)
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())
# 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")))
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_row_widgets()
self.player_buttons = glassbuttons.PlayerButtons()
self.player_buttons.widget.set_tooltip_text(_("Prev Frame - Arrow Left\nNext Frame - Arrow Right\nPlay - Space\nStop - Space\nMark In - I\nMark Out - O\nClear Marks\nTo Mark In\nTo Mark Out"))
player_buttons_row = Gtk.HBox(False, 0)
player_buttons_row.pack_start(self.player_buttons.widget, False, True, 0)
player_buttons_row.pack_start(pos_bar_frame, True, True, 0)
player_buttons_row.set_margin_bottom(2)
# Creates monitor switch buttons
self._create_monitor_buttons()
# Monitor top info row
monitor_info_row = Gtk.HBox(False, 1)
monitor_info_row.pack_start(self.monitor_source, False, False, 0)
monitor_info_row.pack_start(Gtk.Label(), True, False, 0)
monitor_info_row.pack_start(self.info1, False, False, 0)
# Switch / pos bar row
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))
sw_pos_hbox = Gtk.HBox(False, 1)
sw_pos_hbox.pack_start(self.sequence_editor_b, True, True, 0)
sw_pos_hbox.pack_start(self.clip_editor_b, True, True, 0)
sw_pos_hbox.pack_start(self.trim_view_select.widget, False, False, 0)
sw_pos_hbox.pack_start(self.view_mode_select.widget, False, False, 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_info_row, False, True, 0)
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)
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.dark_theme == False:
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(editevent.insert_move_mode_pressed,
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)
info_h.set_size_request(tlinewidgets.COLUMN_WIDTH - 22 - 22 - 22,# - 22, # room for 3 menu launch buttons
tlinewidgets.SCALE_HEIGHT)
marker_surface = cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "marker.png")
markers_launcher = guicomponents.get_markers_menu_launcher(tlineaction.marker_menu_lauch_pressed, marker_surface)
tracks_launcher_surface = cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "track_menu_launch.png")
tracks_launcher = guicomponents.PressLaunch(trackaction.all_tracks_menu_launch_pressed, tracks_launcher_surface)
levels_launcher_surface = cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "audio_levels_menu_launch.png")
levels_launcher = guicomponents.PressLaunch(trackaction.audio_levels_menu_launch_pressed, levels_launcher_surface)
# Timeline top row
tline_hbox_1 = Gtk.HBox()
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)
# 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)
#tline_pane.override_background_color(Gtk.StateFlags.NORMAL, gui.get_bg_color())
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
# Pane
pane = Gtk.VBox(False, 1)
pane.pack_start(menu_vbox, False, True, 0)
pane.pack_start(self.app_v_paned, True, True, 0)
# 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(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
self.mm_paned.set_position(editorpersistance.prefs.mm_paned_position)
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)
mb_menu_item = Gtk.MenuItem(_("Middlebar Layout").encode('utf-8'))
mb_menu = Gtk.Menu()
tc_left = Gtk.RadioMenuItem()
tc_left.set_label(_("Timecode Left").encode('utf-8'))
#tc_left.set_active(appconsts)
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").encode('utf-8'))
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").encode('utf-8'))
components_centered.connect("activate", lambda w: middlebar._show_buttons_COMPONETS_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").encode('utf-8'))
tabs_menu = Gtk.Menu()
tabs_up = Gtk.RadioMenuItem()
tabs_up.set_label( _("Up").encode('utf-8'))
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").encode('utf-8'))
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)
show_monitor_info_item = Gtk.CheckMenuItem(_("Show Monitor Sequence Profile").encode('utf-8'))
show_monitor_info_item.set_active(editorpersistance.prefs.show_sequence_profile)
show_monitor_info_item.connect("toggled", lambda w: middlebar._show_monitor_info_toggled(w))
menu.append(show_monitor_info_item)
show_vu_item = Gtk.CheckMenuItem(_("Show Master Volume Meter").encode('utf-8'))
show_vu_item.set_active(editorpersistance.prefs.show_vu_meter)
show_vu_item.connect("toggled", lambda w: self._show_vu_meter(w))
menu.append(show_vu_item)
sep = Gtk.SeparatorMenuItem()
menu.append(sep)
interp_menu_item = Gtk.MenuItem(_("Monitor Playback Interpolation").encode('utf-8'))
interp_menu = Gtk.Menu()
interp_nearest = Gtk.RadioMenuItem()
interp_nearest.set_label(_("Nearest Neighbour (fast)").encode('utf-8'))
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)").encode('utf-8'))
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)").encode('utf-8'))
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)").encode('utf-8'))
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").encode('utf-8'))
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").encode('utf-8'))
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").encode('utf-8'))
zoom_fit_menu_item.connect("activate", lambda w: updater.zoom_project_length())
menu.append(zoom_fit_menu_item)
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 _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):
if editorpersistance.prefs.show_vu_meter:
if len(self.top_row_hbox) == 1:
self.top_row_hbox.pack_end(audiomonitoring.get_master_meter(), False, False, 0)
else:
if len(self.top_row_hbox) == 2:
meter = self.top_row_hbox.get_children()[1]
self.top_row_hbox.remove(meter)
audiomonitoring.close_master_meter()
if show_all:
self.window.show_all()
def _create_monitor_buttons(self):
# Monitor switch buttons
self.sequence_editor_b = Gtk.RadioButton(None) #, _("Timeline"))
self.sequence_editor_b.set_mode(False)
self.sequence_editor_b.set_image(Gtk.Image.new_from_file(IMG_PATH + "timeline_button.png"))
self.sequence_editor_b.connect("clicked",
lambda w,e: self._monitor_switch_handler(w),
None)
self.clip_editor_b = Gtk.RadioButton.new_from_widget(self.sequence_editor_b)#,_("Clip"))
self.clip_editor_b.set_mode(False)
self.clip_editor_b.set_image(Gtk.Image.new_from_file(IMG_PATH + "clip_button.png"))
self.clip_editor_b.connect("clicked",
lambda w,e: self._monitor_switch_handler(w),
None)
def _monitor_switch_handler(self, widget):
# We get two "clicked" events per toggle, send through only the one
# from activated button
if ((self.sequence_editor_b.get_active() == True)
and (widget == self.sequence_editor_b)):
updater.display_sequence_in_monitor()
if ((self.clip_editor_b.get_active() == True)
and (widget == self.clip_editor_b)):
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):
modes_pixbufs = [INSERTMOVE_CURSOR, OVERWRITE_CURSOR, ONEROLL_CURSOR, ONEROLL_RIPPLE_CURSOR, TWOROLL_CURSOR, SLIDE_CURSOR, MULTIMOVE_CURSOR, OVERWRITE_BOX_CURSOR]
middlebar.create_edit_buttons_row_buttons(self, modes_pixbufs)
buttons_row = Gtk.HBox(False, 1)
if editorpersistance.prefs.midbar_layout == appconsts.MIDBAR_COMPONENTS_CENTERED:
middlebar.fill_with_COMPONETS_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)
buttons_row.set_margin_top(2)
buttons_row.set_margin_left(2)
buttons_row.set_margin_right(2)
return buttons_row
def _add_tool_tips(self):
self.big_TC.widget.set_tooltip_text(_("Timeline current frame timecode"))
self.view_mode_select.widget.set_tooltip_text(_("Select view mode: Video/Vectorscope/RGBParade"))
self.tc.widget.set_tooltip_text(_("Monitor Sequence/Media current frame timecode"))
self.monitor_source.set_tooltip_text(_("Current Monitor Sequence/Media name"))
self.pos_bar.widget.set_tooltip_text(_("Monitor Sequence/Media current position"))
self.sequence_editor_b.set_tooltip_text(_("Display Current Sequence on Timeline"))
self.clip_editor_b.set_tooltip_text(_("Display Monitor Clip"))
def handle_over_move_mode_button_press(self):
editevent.overwrite_move_mode_pressed()
self.set_cursor_to_mode()
def handle_box_mode_button_press(self):
editevent.box_mode_pressed()
self.set_cursor_to_mode()
def handle_insert_move_mode_button_press(self):
editevent.insert_move_mode_pressed()
self.set_cursor_to_mode()
def handle_one_roll_mode_button_press(self):
editevent.oneroll_trim_no_edit_init()
self.set_cursor_to_mode()
def handle_two_roll_mode_button_press(self):
editevent.tworoll_trim_no_edit_init()
self.set_cursor_to_mode()
def handle_slide_mode_button_press(self):
editevent.slide_trim_no_edit_init()
self.set_cursor_to_mode()
def handle_multi_mode_button_press(self):
editevent.multi_mode_pressed()
self.set_cursor_to_mode()
def toggle_trim_ripple_mode(self):
editorstate.trim_mode_ripple = (editorstate.trim_mode_ripple == False)
editevent.stop_looping()
editorstate.edit_mode = editorstate.ONE_ROLL_TRIM_NO_EDIT
tlinewidgets.set_edit_mode(None, None)
self.set_mode_selector_to_mode()
self.set_tline_cursor(editorstate.EDIT_MODE())
updater.set_trim_mode_gui()
def toggle_overwrite_box_mode(self):
editorstate.overwrite_mode_box = (editorstate.overwrite_mode_box == False)
boxmove.clear_data()
self.set_mode_selector_to_mode()
self.set_tline_cursor(editorstate.EDIT_MODE())
def mode_selector_pressed(self, selector, event):
guicomponents.get_mode_selector_popup_menu(selector, event, self.mode_selector_item_activated)
def mode_selector_item_activated(self, selector, mode):
if mode == 0:
self.handle_insert_move_mode_button_press()
if mode == 1:
self.handle_over_move_mode_button_press()
if mode == 2:
if editorstate.edit_mode != editorstate.ONE_ROLL_TRIM and editorstate.edit_mode != editorstate.ONE_ROLL_TRIM_NO_EDIT:
self.handle_one_roll_mode_button_press()
else:
self.toggle_trim_ripple_mode()
if mode == 3:
self.handle_two_roll_mode_button_press()
if mode == 4:
self.handle_slide_mode_button_press()
if mode == 5:
self.handle_multi_mode_button_press()
if mode == 6:
self.handle_box_mode_button_press()
self.set_cursor_to_mode()
self.set_mode_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, 6, 15)
else:
cursor = self.get_own_cursor(display, OVERWRITE_BOX_CURSOR, 6, 15)
elif mode == editorstate.TWO_ROLL_TRIM:
cursor = self.get_own_cursor(display, TWOROLL_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_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_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.MULTI_MOVE:
cursor = self.get_own_cursor(display, MULTIMOVE_CURSOR, 4, 8)
elif mode == editorstate.CLIP_END_DRAG:
cursor = Gdk.Cursor.new(Gdk.CursorType.SB_H_DOUBLE_ARROW)
else:
cursor = Gdk.Cursor.new(Gdk.CursorType.LEFT_PTR)
gdk_window.set_cursor(cursor)
def set_mode_selector_to_mode(self):
if editorstate.EDIT_MODE() == editorstate.INSERT_MOVE:
self.modes_selector.set_pixbuf(0)
elif editorstate.EDIT_MODE() == editorstate.OVERWRITE_MOVE:
if editorstate.overwrite_mode_box == False:
self.modes_selector.set_pixbuf(1)
else:
self.modes_selector.set_pixbuf(7)
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.modes_selector.set_pixbuf(2)
else:
self.modes_selector.set_pixbuf(3)
elif editorstate.EDIT_MODE() == editorstate.TWO_ROLL_TRIM:
self.modes_selector.set_pixbuf(4)
elif editorstate.EDIT_MODE() == editorstate.TWO_ROLL_TRIM_NO_EDIT:
self.modes_selector.set_pixbuf(4)
elif editorstate.EDIT_MODE() == editorstate.SLIDE_TRIM:
self.modes_selector.set_pixbuf(5)
elif editorstate.EDIT_MODE() == editorstate.SLIDE_TRIM_NO_EDIT:
self.modes_selector.set_pixbuf(5)
elif editorstate.EDIT_MODE() == editorstate.MULTI_MOVE:
self.modes_selector.set_pixbuf(6)
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.tc = guicomponents.MonitorTCDisplay()
self.monitor_source = Gtk.Label(label="sequence1")
self.monitor_source.set_ellipsize(Pango.EllipsizeMode.END)
self.monitor_source.modify_font(Pango.FontDescription("sans bold 8"))
self.info1 = Gtk.Label(label="--:--:--:--")
self.info1.set_ellipsize(Pango.EllipsizeMode.END)
self.info1.modify_font(Pango.FontDescription("sans bold 8"))
def _this_is_not_used():
print "THIS WAS USED!!!!!"
flowblade-1.12/flowblade-trunk/Flowblade/exporting.py 0000664 0000000 0000000 00000045173 13062777160 0023007 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
from gi.repository import GLib
import os, sys
from xml.dom import minidom
from decimal import Decimal,getcontext,ROUND_DOWN
from math import modf, floor
import mlt
import time
import md5
import re
import shutil
import appconsts
import dialogs
import dialogutils
from editorstate import PLAYER
from editorstate import PROJECT
from editorstate import current_sequence
import gui
import guiutils
import renderconsumer
import utils
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()
f = open(edl_path, 'w')
f.write(edl_contents)
f.close()
def get_edl_temp_xml_path():
return utils.get_hidden_user_dir_path() + "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 = 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.iteritems():
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 = md5.new(producer_resource).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(md5.new(resource).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 = utils.get_hidden_screenshot_dir_path() + "screenshot_%01d." + ext
rendered_file_path = utils.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 utils.get_hidden_screenshot_dir_path() + "screenshot_%01d.png"
def get_displayed_image_path():
return utils.get_hidden_screenshot_dir_path() + "screenshot_1.png"
def _screenshot_frame_changed(adjustment):
_update_displayed_image(int(adjustment.get_value()))
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").encode('utf-8')
ok_str = _("Export Image").encode('utf-8')
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 = utils.get_hidden_screenshot_dir_path()
for f in os.listdir(d):
os.remove(os.path.join(d, f))
flowblade-1.12/flowblade-trunk/Flowblade/extraeditors.py 0000664 0000000 0000000 00000142011 13062777160 0023472 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 cairo
import cairoarea
import editorpersistance
from editorstate import PLAYER
import gui
import guiutils
import guicomponents
import glassbuttons
import lutfilter
import respaths
import viewgeom
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 = filter(lambda ep: ep.name == "hue", editable_properties)[0]
self.saturation = filter(lambda ep: ep.name == "saturation", editable_properties)[0]
self.R = filter(lambda ep: ep.name == "R", editable_properties)[0]
self.G = filter(lambda ep: ep.name == "G", editable_properties)[0]
self.B = filter(lambda ep: ep.name == "B", editable_properties)[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 = unicode(int(360 * hue)) + ColorGrader.DEGREE_CHAR + u' '
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 = filter(lambda ep: ep.name == "lift_r", editable_properties)[0]
self.lift_g = filter(lambda ep: ep.name == "lift_g", editable_properties)[0]
self.lift_b = filter(lambda ep: ep.name == "lift_b", editable_properties)[0]
self.gamma_r = filter(lambda ep: ep.name == "gamma_r", editable_properties)[0]
self.gamma_g = filter(lambda ep: ep.name == "gamma_g", editable_properties)[0]
self.gamma_b = filter(lambda ep: ep.name == "gamma_b", editable_properties)[0]
self.gain_r = filter(lambda ep: ep.name == "gain_r", editable_properties)[0]
self.gain_g = filter(lambda ep: ep.name == "gain_g", editable_properties)[0]
self.gain_b = filter(lambda ep: ep.name == "gain_b", editable_properties)[0]
# Get Non-MLT properties
self.lift_hue = filter(lambda ep: ep.name == "lift_hue", editable_properties)[0]
self.lift_value = filter(lambda ep: ep.name == "lift_value", editable_properties)[0]
self.gamma_hue = filter(lambda ep: ep.name == "gamma_hue", editable_properties)[0]
self.gamma_value = filter(lambda ep: ep.name == "gamma_value", editable_properties)[0]
self.gain_hue = filter(lambda ep: ep.name == "gain_hue", editable_properties)[0]
self.gain_value = filter(lambda ep: ep.name == "gain_value", editable_properties)[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 = unicode(int(360 * hue)) + ColorGrader.DEGREE_CHAR + u' '
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.dark_theme == False:
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.G
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.dark_theme == True:
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 = u'\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 = filter(lambda ep: ep.name == "shadow_hue", editable_properties)[0]
self.shadow_saturation = filter(lambda ep: ep.name == "shadow_saturation", editable_properties)[0]
self.mid_hue = filter(lambda ep: ep.name == "mid_hue", editable_properties)[0]
self.mid_saturation = filter(lambda ep: ep.name == "mid_saturation", editable_properties)[0]
self.hi_hue = filter(lambda ep: ep.name == "hi_hue", editable_properties)[0]
self.hi_saturation = filter(lambda ep: ep.name == "hi_saturation", editable_properties)[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 = unicode(int(360 * hue)) + ColorGrader.DEGREE_CHAR + u' '
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-1.12/flowblade-trunk/Flowblade/glassbuttons.py 0000664 0000000 0000000 00000050767 13062777160 0023525 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
from gi.repository import Gtk
import cairoarea
import editorpersistance
import gui
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
MB_BUTTONS_WIDTH = 317
MB_BUTTONS_HEIGHT = 30
MB_BUTTON_HEIGHT = 22
MB_BUTTON_WIDTH = 35
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:[]}
#FLAT_COLOR_LIGHT = (0.9, 0.9, 0.9)
#FLAT_COLOR_DARK = (0.25, 0.25, 0.25)
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.dark_theme == True:
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()
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
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):
AbstractGlassButtons.__init__(self, MB_BUTTON_WIDTH, MB_BUTTON_HEIGHT, MB_BUTTON_Y, MB_BUTTONS_WIDTH, MB_BUTTONS_HEIGHT)
IMG_PATH = respaths.IMAGE_PATH
# Jul-2016 - SvdB - Modified to replace play/stop combo by single play/pause button, if option is set
play_pause_icon = cairo.ImageSurface.create_from_png(IMG_PATH + "play_pause_s.png")
#
play_icon = cairo.ImageSurface.create_from_png(IMG_PATH + "play_2_s.png")
stop_icon = cairo.ImageSurface.create_from_png(IMG_PATH + "stop_s.png")
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")
# 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 = [8, 10, 8, 6, 14, 5, 10, 9]
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 = [8, 10, 13, 13, 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_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, MB_BUTTON_HEIGHT, MB_BUTTON_Y, GMIC_BUTTONS_WIDTH, MB_BUTTONS_HEIGHT)
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
def focus_group_has_focus(focus_group):
group = focus_groups[focus_group]
for widget in group:
if widget.is_focus():
return True
return False
flowblade-1.12/flowblade-trunk/Flowblade/gui.py 0000664 0000000 0000000 00000017444 13062777160 0021554 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.
"""
from gi.repository import Gtk, Gdk
import pickle
import editorpersistance
import utils
# Editor window
editor_window = None
# Menu
editmenu = None
# Project data lists
media_list_view = None
bin_list_view = None
sequence_list_view = None
effect_stack_list_view = None
middle_notebook = None # This is now the only notebook, 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
proxy_button = None
# Monitor
pos_bar = None
tc = None
# Timeline
tline_display = None
tline_scale = None
tline_canvas = None
tline_scroll = None
tline_info = None
tline_column = None
tline_left_corner = None
big_tc = None
monitor_widget = None
# indexes match editmode values in editorstate.py
notebook_buttons = None
play_b = None
clip_editor_b = 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)")
_THEME_COLORS = (_UBUNTU_COLORS, _GNOME_COLORS, _MINT_COLORS, _ARC_COLORS)
_CURRENT_THEME_COLORS_FILE = "currentcolors.data"
_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, \
tc, tline_display, tline_scale, tline_canvas, tline_scroll, tline_v_scroll, tline_info, \
tline_column, play_b, clip_editor_b, sequence_editor_b, \
effect_select_list_view, effect_select_combo_box, project_info_vbox, middle_notebook, big_tc, editmenu, notebook_buttons, tline_left_corner, \
monitor_widget
editor_window = new_editor_window
media_list_view = editor_window.media_list_view
bin_list_view = editor_window.bin_list_view
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
tc = editor_window.tc
monitor_widget = editor_window.monitor_widget
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
clip_editor_b = editor_window.clip_editor_b
sequence_editor_b = editor_window.sequence_editor_b
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.dark_theme == False:
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)
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.dark_theme == False:
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
# 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 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()
write_file = file(save_file_path, "wb")
pickle.dump(colors, write_file)
def load_current_colors():
load_path = _colors_data_path()
f = open(load_path)
colors = pickle.load(f)
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 utils.get_hidden_user_dir_path() + _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
flowblade-1.12/flowblade-trunk/Flowblade/guicomponents.py 0000664 0000000 0000000 00000263142 13062777160 0023660 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
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
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 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()
# ------------------------------------------------- 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 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)
# 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
class SequenceListView(ImageTextTextListView):
"""
GUI component displaying list of sequences in project
"""
def __init__(self, seq_name_edited_cb):
ImageTextTextListView.__init__(self)
# 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))
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()
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, 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.VBox):
def __init__(self):
GObject.GObject.__init__(self)
self.name_label = guiutils.bold_label(_("Clip:"))
self.name_value = Gtk.Label()
self.name_value.set_ellipsize(Pango.EllipsizeMode.END)
self.name_value.set_max_width_chars(15)
self.track = guiutils.bold_label(_("Track:"))
self.track_value = Gtk.Label()
self.position = guiutils.bold_label(_("Pos:"))
self.position_value = Gtk.Label()
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)
info_row_3 = Gtk.HBox()
info_row_3.pack_start(self.position, False, False, 0)
info_row_3.pack_start(self.position_value, True, True, 0)
self.pack_start(info_row_1, False, False, 0)
self.pack_start(info_row_2, False, False, 0)
self.pack_start(info_row_3, False, False, 0)
self.set_spacing(4)
if editorstate.screen_size_small_height():
self.set_size_request(CLIP_EDITOR_LEFT_WIDTH, 10)
else:
self.set_size_request(CLIP_EDITOR_LEFT_WIDTH, 56)
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.position.set_text(_("Position:"))
clip_start_in_tline = track.clip_start(index)
tc_str = utils.get_tc_string(clip_start_in_tline)
self.position_value.set_text(tc_str)
self._set_use_mark_up()
def set_no_clip_info(self):
self.name_label.set_text(_("Clip:"))
self.name_value.set_text("")
self.track.set_text(_("Track:"))
self.track_value.set_text("")
self.position.set_text(_("Position:"))
self.position_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.position.set_use_markup(True)
def set_enabled(self, value):
self.name_label.set_sensitive(value)
self.track.set_sensitive(value)
self.position.set_sensitive(value)
class CompositorInfoPanel(Gtk.VBox):
def __init__(self):
GObject.GObject.__init__(self)
self.set_homogeneous(False)
self.source_track = Gtk.Label()
self.source_track_value = Gtk.Label()
self.destination_track = Gtk.Label()
self.destination_track_value = Gtk.Label()
self.position = Gtk.Label()
self.position_value = Gtk.Label()
self.length = Gtk.Label()
self.length_value = Gtk.Label()
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_4 = Gtk.HBox()
info_row_4.pack_start(self.position, False, False, 0)
info_row_4.pack_start(self.position_value, False, False, 0)
info_row_4.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_4, 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):
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)
pos = utils.get_tc_string(compositor.clip_in)
self.position_value.set_text(pos)
length = utils.get_tc_string(compositor.clip_out - compositor.clip_in)
self.length_value.set_text(length)
def set_no_compositor_info(self):
self.source_track.set_text(_("Source Track:") + " ")
self.source_track_value.set_text("")
self.destination_track.set_text(_("Destination Track:") + " ")
self.destination_track_value.set_text("")
self.position.set_text(_("Position:") + " ")
self.position_value.set_text("")
self.length.set_text(_("Length:") + " ")
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.position.set_use_markup(True)
self.length.set_use_markup(True)
def set_enabled(self, value):
self.source_track.set_sensitive(value)
self.destination_track.set_sensitive(value)
self.position.set_sensitive(value)
self.length.set_sensitive(value)
# -------------------------------------------- media select panel
class MediaPanel():
def __init__(self, media_file_popup_cb, double_click_cb):
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.double_click_cb = double_click_cb
self.monitor_indicator = cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "monitor_indicator.png")
self.last_event_time = 0.0
global has_proxy_icon, is_proxy_icon, graphics_icon, imgseq_icon, audio_icon, pattern_icon, profile_warning_icon
has_proxy_icon = cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "has_proxy_indicator.png")
is_proxy_icon = cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "is_proxy_indicator.png")
graphics_icon = cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "graphics_indicator.png")
imgseq_icon = cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "imgseq_indicator.png")
audio_icon = cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "audio_indicator.png")
pattern_icon = cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "pattern_producer_indicator.png")
profile_warning_icon = cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "profile_warning.png")
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:
widget.grab_focus()
self.double_click_cb(media_object.media_file)
# 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):
media_object.widget.override_background_color(Gtk.StateType.NORMAL, gui.get_selected_bg_color())
# add to selected if not already there, otherwise remove
try:
self.selected_objects.index(media_object)
self.selected_objects.remove(media_object)
bg_color = gui.get_bg_color()
media_object.widget.override_background_color(Gtk.StateType.NORMAL, bg_color)
return True
except:
self.selected_objects.append(media_object)
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 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 empty_pressed(self, widget, event):
self.clear_selection()
def select_all(self):
self.clear_selection()
bg_color = gui.get_selected_bg_color()
for media_file, media_object in self.widget_for_mediafile.iteritems():
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 = []
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, 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):
filler = Gtk.EventBox()
filler.connect("button-press-event", lambda w,e: self.empty_pressed(w,e))
filler.add(Gtk.Label())
return filler
class MediaObjectWidget:
def __init__(self, media_file, selected_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.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, 3, 2, 3, 2)
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
cr.set_source_surface(self.media_file.icon, 0, 0)
cr.paint()
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,75,62,12)
cr.fill()
cr.move_to(30, 84)
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()
# -------------------------------------------- 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):
surface_list = [cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "program_view_2.png"),
cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "vectorscope.png"),
cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "rgbparade.png")]
menu_launch = ImageMenuLaunch(callback, surface_list, w=24, h=20)
menu_launch.surface_y = 10
return menu_launch
def get_trim_view_select_combo(callback):
surface = cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "trim_view.png")
menu_launch = PressLaunch(callback, surface, w=24, h=20)
menu_launch.surface_y = 10
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)
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:
clip_menu.add(_get_menu_item(_("Split Audio"), callback,\
(clip, track, "split_audio", event.x), True))
if track.id == current_sequence().first_video_index:
active = True
else:
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))
_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))
# Only add compositors for video tracks V2 and higher
if track.id <= current_sequence().first_video_index:
active = False
else:
active = True
clip_menu.add(_get_compositors_add_menu_item(event, clip, track, callback, active))
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)))
_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))
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))
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(_("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
clip_menu.add(_get_compositors_add_menu_item(event, clip, track, callback, active))
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))
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)))
_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:
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
if compositor_type == "##affine":
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()
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(_("Add Blend"))
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
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_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_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_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 _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.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)
"""
if media_file.type == appconsts.VIDEO:
if media_file.info != None:
best_media_profile_index = mltprofiles.get_closest_matching_profile_index(media_file.info)
project_profile_index = mltprofiles.get_index_for_name(PROJECT().profile.description())
# Add this item if best profile does not match project profile
if best_media_profile_index != project_profile_index:
_add_separetor(media_file_menu)
item = _get_menu_item(_("Change Project Profile To Match..."), callback, ("Project Profile", 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")
str_list.append(_("Fps: ") + str(profile.fps()))
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 = []
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(str(profile.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):
self.widget = cairoarea.CairoDrawableArea2( 170,
22,
self._draw)
self.font_desc = Pango.FontDescription("Bitstream Vera Sans Mono Condensed 15")
# Draw consts
x = 2
y = 2
width = 166
height = 24
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
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)
cr.set_source_rgb(0.2, 0.2, 0.2)
cr.fill_preserve()
if editorpersistance.prefs.dark_theme == False:
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 ()
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 bg
#cr.set_source_rgb(*guiutils.get_theme_bg_color())
#cr.rectangle(0, 0, w, h)
#cr.fill()
# Draw round rect with gradient and stroke around for thin bezel
self._round_rect_path(cr)
cr.set_source_rgb(0.2, 0.2, 0.2)
cr.fill_preserve()
if editorpersistance.prefs.dark_theme == False:
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 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.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, 8, 1)
self.video_tracks.set_value(v_tracks)
#self.video_tracks.set_editable(False)
self.video_tracks.connect("value-changed", self.video_tracks_changed)
self.audio_label = Gtk.Label(_("Audio:"))
self.audio_tracks = Gtk.SpinButton.new_with_range(1, 8, 1)
self.audio_tracks.set_value(a_tracks)
#self.audio_tracks.set_editable(False)
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())) + " / 9")
self.tracks_amount_info.queue_draw ()
def get_tracks(self):
return (int(self.video_tracks.get_value()), int(self.audio_tracks.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
def get_markers_menu_launcher(callback, pixbuf):
m_launch = PressLaunch(callback, pixbuf)
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" ))
menu.popup(None, None, None, None, event.button, event.time)
def get_audio_levels_popup_menu(event, callback):
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)
show_magnet_item = Gtk.CheckMenuItem()
show_magnet_item.set_label(_("Show Magnet Icon"))
show_magnet_item.set_active(snapping.show_magnet_icon)
show_magnet_item.connect("activate", callback, "magnet")
menu.append(show_magnet_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_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").encode('utf-8'))
overlay_menu_item.show()
overlay_menu = Gtk.Menu()
op_100 = Gtk.RadioMenuItem()
op_100.set_label(_("100%").encode('utf-8'))
op_100.connect("activate", callback, 3)
op_100.show()
overlay_menu.append(op_100)
op_80 = Gtk.RadioMenuItem.new_with_label([op_100], _("80%").encode('utf-8'))
op_80.connect("activate", callback, 4)
op_80.show()
overlay_menu.append(op_80)
op_50 = Gtk.RadioMenuItem.new_with_label([op_100], _("50%").encode('utf-8'))
op_50.connect("activate", callback, 5)
op_50.show()
overlay_menu.append(op_50)
op_20 = Gtk.RadioMenuItem.new_with_label([op_100], _("20%").encode('utf-8'))
op_20.connect("activate", callback, 6)
op_20.show()
overlay_menu.append(op_20)
op_0 = Gtk.RadioMenuItem.new_with_label([op_100], _("0%").encode('utf-8'))
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").encode('utf-8'))
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").encode('utf-8'))
trim_view_single.show()
menu.append(trim_view_single)
no_trim_view = Gtk.RadioMenuItem.new_with_label([trim_view_all], _("Trim View Off").encode('utf-8'))
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_mode_selector_popup_menu(launcher, event, callback):
menu = tools_menu
guiutils.remove_children(menu)
menu.set_accel_group(gui.editor_window.accel_group)
menu.set_take_focus(False)
menu_items = []
menu_item = _get_image_menu_item(Gtk.Image.new_from_file(
respaths.IMAGE_PATH + "insertmove_cursor.png"), _("Insert"), callback, 0)
menu_item.set_accel_path("/WindowActions/InsertMode")
menu.add(menu_item)
menu_items.append(menu_item)
menu_item = _get_image_menu_item(Gtk.Image.new_from_file(
respaths.IMAGE_PATH + "overwrite_cursor.png"), _("Overwrite"), callback, 1)
menu_item.set_accel_path("/WindowActions/OverMode")
menu.add(menu_item)
menu_items.append(menu_item)
menu_item = _get_image_menu_item(Gtk.Image.new_from_file(
respaths.IMAGE_PATH + "oneroll_cursor.png"), _("Trim"), callback, 2)
menu_item.set_accel_path("/WindowActions/OneRollMode")
menu.add(menu_item)
menu_items.append(menu_item)
menu_item = _get_image_menu_item(Gtk.Image.new_from_file(
respaths.IMAGE_PATH + "tworoll_cursor.png"), _("Roll"), callback, 3)
menu_item.set_accel_path("/WindowActions/TwoRollMode")
menu.add(menu_item)
menu_items.append(menu_item)
menu_item = _get_image_menu_item(Gtk.Image.new_from_file(
respaths.IMAGE_PATH + "slide_cursor.png"), _("Slip"), callback, 4)
menu_item.set_accel_path("/WindowActions/SlideMode")
menu.add(menu_item)
menu_items.append(menu_item)
menu_item = _get_image_menu_item(Gtk.Image.new_from_file(
respaths.IMAGE_PATH + "multimove_cursor.png"), _("Spacer"), callback, 5)
menu_item.set_accel_path("/WindowActions/MultiMode")
menu.add(menu_item)
menu_items.append(menu_item)
menu_item = _get_image_menu_item(Gtk.Image.new_from_file(
respaths.IMAGE_PATH + "overwrite_cursor_box.png"), _("Box"), callback, 6)
menu_item.set_accel_path("/WindowActions/BoxMode")
menu.add(menu_item)
menu_items.append(menu_item)
menu.connect("hide", lambda w : _tools_menu_hidden(w,menu_items))
menu.show_all()
menu.popup(None, None, None, None, event.button, event.time)
def _tools_menu_hidden(tools_menu, menu_items):
# needed to make number 1-6 work elsewhere in the application
for menu_item in menu_items:
menu_item.set_accel_path(None)
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)
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 _draw(self, event, cr, allocation):
PressLaunch._draw(self, event, cr, allocation)
cr.move_to(27, 13)
cr.line_to(32, 18)
cr.line_to(37, 13)
cr.close_path()
if editorpersistance.prefs.dark_theme == False:
cr.set_source_rgb(0, 0, 0)
else:
cr.set_source_rgb(0.66, 0.66, 0.66)
cr.fill()
flowblade-1.12/flowblade-trunk/Flowblade/guiutils.py 0000664 0000000 0000000 00000024741 13062777160 0022633 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 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(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_image_button(img_file_name, width, height):
button = Gtk.Button()
icon = Gtk.Image.new_from_file(respaths.IMAGE_PATH + img_file_name)
button_box = Gtk.HBox()
button_box.pack_start(icon, False, False, 0)
button.add(button_box)
button.set_size_request(width, height)
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) # patching in to make 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_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):
"""
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)
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 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-1.12/flowblade-trunk/Flowblade/jackaudio.py 0000664 0000000 0000000 00000016663 13062777160 0022724 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 .
"""
from gi.repository import Gtk
import commands
import dbus
import os
import threading
import time
import appconsts
import dialogutils
import editorpersistance
import editorstate
import guiutils
import utils
_jack_frequencies = [22050, 32000, 44100, 48000, 88200, 96000, 192000]
_jack_failsafe_path = utils.get_hidden_user_dir_path() + "/jack_fail_safe"
_dialog = None
def start_up():
pass
def use_jack_clicked(window):
jackstart_thread = JackStartThread(window)
jackstart_thread.start()
class JackChangeThread(threading.Thread):
def __init__(self, window):
threading.Thread.__init__(self)
self.window = window
class JackStartThread(JackChangeThread):
def run(self):
editorstate.PLAYER().jack_output_on()
time.sleep(1.0)
Gdk.threads_enter()
self.window.set_gui_state()
Gdk.threads_leave()
def frequency_changed(freq_index):
editorpersistance.prefs.jack_frequency = _jack_frequencies[freq_index]
editorpersistance.save()
def start_op_changed(w):
if w.get_active() == True:
editorpersistance.prefs.jack_start_up_op = appconsts.JACK_ON_START_UP_YES
else:
editorpersistance.prefs.jack_start_up_op = appconsts.JACK_ON_START_UP_NO
editorpersistance.save()
def output_type_changed(output_type):
editorpersistance.prefs.jack_output_type = output_type
editorpersistance.save()
def delete_failsafe_file():
try:
os.remove(_jack_failsafe_path)
except:
pass
class JackAudioManagerDialog:
def __init__(self):
self.dialog = Gtk.Dialog(_("JACK Audio Manager"), None,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(_("Close").encode('utf-8'), Gtk.ResponseType.CLOSE))
start_up_label = Gtk.Label(label=_("Start JACK output on application start-up"))
self.startup_check_button = Gtk.CheckButton()
if editorpersistance.prefs.jack_start_up_op == appconsts.JACK_ON_START_UP_YES:
self.startup_check_button.set_active(True)
self.startup_check_button.connect("toggled",
lambda w,e: start_op_changed(w),
None)
start_row = guiutils.get_checkbox_row_box(self.startup_check_button, start_up_label)
self.frequency_select = Gtk.ComboBoxText()
cur_value_index = 0
count = 0
for freq in _jack_frequencies:
self.frequency_select.append_text(str(freq))
if freq == editorpersistance.prefs.jack_frequency:
cur_value_index = count
count = count + 1
self.frequency_select.set_active(cur_value_index)
self.frequency_select.connect("changed",
lambda w,e: frequency_changed(w.get_active()),
None)
freq_row = guiutils.get_two_column_box_right_pad(Gtk.Label(label="JACK frequency Hz:"), self.frequency_select, 190, 15)
self.output_type_select = Gtk.ComboBoxText()
self.output_type_select.append_text(_("Audio"))
self.output_type_select.append_text(_("Sync Master Timecode"))
# Indexes correspond with appconsts.JACK_OUT_AUDIO, appconsts.JACK_OUT_SYNC values
self.output_type_select.set_active(editorpersistance.prefs.jack_output_type)
self.output_type_select.connect("changed",
lambda w,e: output_type_changed(w.get_active()),
None)
output_row = guiutils.get_two_column_box_right_pad(Gtk.Label(label="JACK output type:"), self.output_type_select, 190, 15)
vbox_props = Gtk.VBox(False, 2)
vbox_props.pack_start(freq_row, False, False, 0)
vbox_props.pack_start(output_row, False, False, 0)
vbox_props.pack_start(start_row, False, False, 0)
vbox_props.pack_start(guiutils.pad_label(8, 12), False, False, 0)
props_frame = guiutils.get_named_frame(_("Properties"), vbox_props)
self.jack_output_status_value = Gtk.Label(label="OFF")
self.jack_output_status_value.set_use_markup(True)
self.jack_output_status_label = Gtk.Label(label="JACK output is ")
status_row = guiutils.get_centered_box([self.jack_output_status_label, self.jack_output_status_value])
self.dont_use_button = Gtk.Button(_("Stop JACK Output"))
self.use_button = Gtk.Button(_("Start JACK Output"))
self.use_button.connect("clicked", lambda w: use_jack_clicked(self))
self.dont_use_button.connect("clicked", lambda w: _convert_to_original_media_project())
self.set_gui_state()
c_box_2 = Gtk.HBox(True, 8)
c_box_2.pack_start(self.dont_use_button, True, True, 0)
c_box_2.pack_start(self.use_button, True, True, 0)
row2_onoff = Gtk.HBox(False, 2)
row2_onoff.pack_start(Gtk.Label(), True, True, 0)
row2_onoff.pack_start(c_box_2, False, False, 0)
row2_onoff.pack_start(Gtk.Label(), True, True, 0)
vbox_onoff = Gtk.VBox(False, 2)
vbox_onoff.pack_start(guiutils.pad_label(12, 4), False, False, 0)
vbox_onoff.pack_start(status_row, False, False, 0)
vbox_onoff.pack_start(guiutils.pad_label(12, 12), False, False, 0)
vbox_onoff.pack_start(row2_onoff, False, False, 0)
onoff_frame = guiutils.get_named_frame(_("Output Status"), vbox_onoff)
# Pane
vbox = Gtk.VBox(False, 2)
vbox.pack_start(props_frame, False, False, 0)
vbox.pack_start(onoff_frame, False, False, 0)
#alignment = Gtk.Alignment.new(0.5, 0.5, 1.0, 1.0)
alignment.set_padding(12, 12, 12, 12)
alignment.add(vbox)
self.dialog.vbox.pack_start(alignment, True, True, 0)
dialogutils.default_behaviour(self.dialog)
self.dialog.connect('response', dialogutils.dialog_destroy)
self.dialog.show_all()
global _dialog
_dialog = self
def set_gui_state(self):
if editorstate.PLAYER().jack_output_filter != None:
self.use_button.set_sensitive(False)
self.dont_use_button.set_sensitive(True)
self.jack_output_status_value.set_text("ON")
self.jack_output_status_value.set_use_markup(True)
else:
self.dont_use_button.set_sensitive(False)
self.use_button.set_sensitive(True)
self.jack_output_status_value.set_text("OFF")
self.jack_output_status_value.set_use_markup(True)
flowblade-1.12/flowblade-trunk/Flowblade/keyevents.py 0000664 0000000 0000000 00000053762 13062777160 0023010 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 Gtk
from gi.repository import Gdk
import audiowaveform
import clipeffectseditor
import compositeeditor
import compositormodes
import glassbuttons
import gui
import editevent
import editorpersistance
import editorstate
from editorstate import current_sequence
from editorstate import PLAYER
from editorstate import timeline_visible
import keyframeeditor
import medialog
import menuactions
import monitorevent
import mltrefhold
import tlineaction
import tlinewidgets
import trimmodes
import updater
import projectaction
import audiowaveformrenderer
# ------------------------------------- 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
else:
if editorstate.current_is_move_mode() == False:
editevent.set_default_edit_mode()
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_tline_focus_events(event)
if was_handled:
# Stop event handling here
return True
# Pressing timeline button obivously leaves user expecting
# to have focus in timeline
if gui.sequence_editor_b.has_focus():
_handle_tline_key_event(event)
# Stop event handling here
return True
# Clip button or posbar focus with clip displayed leaves playback keyshortcuts available
if (gui.clip_editor_b.has_focus()
or (gui.pos_bar.widget.is_focus() and (not timeline_visible()))):
_handle_clip_key_event(event)
# Stop event handling here
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
if event.keyval == Gdk.KEY_F12:
if (event.get_state() & Gdk.ModifierType.CONTROL_MASK):
pass
return True
# Key event was not handled here.
return False
def _timeline_has_focus():
if(gui.tline_canvas.widget.is_focus()
or gui.tline_column.widget.is_focus()
or gui.editor_window.modes_selector.widget.is_focus()
or (gui.pos_bar.widget.is_focus() and timeline_visible())
or gui.tline_scale.widget.is_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.
"""
# I
if event.keyval == Gdk.KEY_i:
if (event.get_state() & Gdk.ModifierType.MOD1_MASK):
monitorevent.to_mark_in_pressed()
return True
monitorevent.mark_in_pressed()
return True
if event.keyval == Gdk.KEY_I:
if (event.get_state() & Gdk.ModifierType.MOD1_MASK):
monitorevent.to_mark_in_pressed()
return True
monitorevent.to_mark_in_pressed()
return True
# O
if event.keyval == Gdk.KEY_o:
if (event.get_state() & Gdk.ModifierType.MOD1_MASK):
monitorevent.to_mark_out_pressed()
return True
monitorevent.mark_out_pressed()
return True
if event.keyval == Gdk.KEY_O:
if (event.get_state() & Gdk.ModifierType.MOD1_MASK):
monitorevent.to_mark_out_pressed()
return True
monitorevent.to_mark_out_pressed()
return True
# SPACE
if event.keyval == Gdk.KEY_space:
if PLAYER().is_playing():
monitorevent.stop_pressed()
else:
monitorevent.play_pressed()
return True
# TAB
if event.keyval == Gdk.KEY_Tab:
updater.switch_monitor_display()
return True
# M
if event.keyval == Gdk.KEY_m:
tlineaction.add_marker()
return True
# Number edit mode changes
if event.keyval == Gdk.KEY_1:
gui.editor_window.handle_insert_move_mode_button_press()
gui.editor_window.set_mode_selector_to_mode()
return True
if event.keyval == Gdk.KEY_2:
gui.editor_window.handle_over_move_mode_button_press()
gui.editor_window.set_mode_selector_to_mode()
return True
if event.keyval == Gdk.KEY_3:
gui.editor_window.handle_one_roll_mode_button_press()
gui.editor_window.set_mode_selector_to_mode()
return True
if event.keyval == Gdk.KEY_4:
gui.editor_window.handle_two_roll_mode_button_press()
gui.editor_window.set_mode_selector_to_mode()
return True
if event.keyval == Gdk.KEY_5:
gui.editor_window.handle_slide_mode_button_press()
gui.editor_window.set_mode_selector_to_mode()
return True
if event.keyval == Gdk.KEY_6:
gui.editor_window.handle_multi_mode_button_press()
gui.editor_window.set_mode_selector_to_mode()
return True
if event.keyval == Gdk.KEY_7:
gui.editor_window.handle_box_mode_button_press()
gui.editor_window.set_mode_selector_to_mode()
return True
# X
if event.keyval == Gdk.KEY_x:
tlineaction.cut_pressed()
return True
# G
if event.keyval == Gdk.KEY_g:
medialog.log_range_clicked()
return True
# R
if event.keyval == Gdk.KEY_r:
gui.editor_window.toggle_trim_ripple_mode()
return True
# Key bindings for keyboard trimming
if editorstate.current_is_active_trim_mode() == True:
# LEFT ARROW, prev frame
if event.keyval == Gdk.KEY_Left:
trimmodes.left_arrow_pressed((event.get_state() & Gdk.ModifierType.CONTROL_MASK))
return True
# RIGHT ARROW, next frame
if event.keyval == Gdk.KEY_Right:
trimmodes.right_arrow_pressed((event.get_state() & Gdk.ModifierType.CONTROL_MASK))
return True
if event.keyval == Gdk.KEY_Return:
trimmodes.enter_pressed()
return True
# Key bindings for MOVE MODES and _NO_EDIT modes
if editorstate.current_is_move_mode() or editorstate.current_is_active_trim_mode() == False:
# UP ARROW, next cut
if event.keyval == Gdk.KEY_Up:
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()
# DOWN ARROW, prev cut
if event.keyval == Gdk.KEY_Down:
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
# LEFT ARROW, prev frame
if event.keyval == Gdk.KEY_Left:
if (event.get_state() & Gdk.ModifierType.CONTROL_MASK):
PLAYER().seek_delta(-10)
else:
PLAYER().seek_delta(-1)
return True
# RIGHT ARROW, next frame
if event.keyval == Gdk.KEY_Right:
if (event.get_state() & Gdk.ModifierType.CONTROL_MASK):
PLAYER().seek_delta(10)
else:
PLAYER().seek_delta(1)
return True
# T
if event.keyval == Gdk.KEY_t:
tlineaction.three_point_overwrite_pressed()
return True
# Y
if event.keyval == Gdk.KEY_y:
if not (event.get_state() & Gdk.ModifierType.CONTROL_MASK):
tlineaction.insert_button_pressed()
return True
# U
if event.keyval == Gdk.KEY_u:
tlineaction.append_button_pressed()
return True
# J
if event.keyval == Gdk.KEY_j:
monitorevent.j_pressed()
return True
# K
if event.keyval == Gdk.KEY_k:
monitorevent.k_pressed()
return True
# L
if event.keyval == Gdk.KEY_l:
if (event.get_state() & Gdk.ModifierType.CONTROL_MASK):
medialog.log_range_clicked()
else:
monitorevent.l_pressed()
return True
# S
if event.keyval == Gdk.KEY_s:
tlineaction.resync_button_pressed()
return True
# DELETE
if event.keyval == Gdk.KEY_Delete:
# 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()
# 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
else:
# HOME
if event.keyval == Gdk.KEY_Home:
if PLAYER().is_playing():
monitorevent.stop_pressed()
gui.editor_window.handle_insert_move_mode_button_press()
gui.editor_window.set_mode_selector_to_mode()
PLAYER().seek_frame(0)
_move_to_beginning()
return True
# END
if event.keyval == Gdk.KEY_End:
if PLAYER().is_playing():
monitorevent.stop_pressed()
gui.editor_window.handle_insert_move_mode_button_press()
gui.editor_window.set_mode_selector_to_mode()
PLAYER().seek_end()
_move_to_end()
return True
return False
def _handle_extended_tline_focus_events(event):
# This was added to fix to a bug long time ago but the rationale for "extended_tline_focus_events" has been forgotten, but probably still exists
if not(_timeline_has_focus() or
gui.pos_bar.widget.is_focus() or
gui.sequence_editor_b.has_focus() or
gui.clip_editor_b.has_focus()):
return False
# T
if event.keyval == Gdk.KEY_t:
tlineaction.three_point_overwrite_pressed()
return True
# Y
if event.keyval == Gdk.KEY_y:
if not (event.get_state() & Gdk.ModifierType.CONTROL_MASK):
tlineaction.insert_button_pressed()
return True
# U
if event.keyval == Gdk.KEY_u:
tlineaction.append_button_pressed()
return True
# J
if event.keyval == Gdk.KEY_j:
monitorevent.j_pressed()
return True
# K
if event.keyval == Gdk.KEY_k:
monitorevent.k_pressed()
return True
# L
if event.keyval == Gdk.KEY_l:
if (event.get_state() & Gdk.ModifierType.CONTROL_MASK):
medialog.log_range_clicked()
else:
monitorevent.l_pressed()
return True
# TAB
if event.keyval == Gdk.KEY_Tab:
updater.switch_monitor_display()
return True
# G
if event.keyval == Gdk.KEY_g:
medialog.log_range_clicked()
return True
# Number edit mode changes
if event.keyval == Gdk.KEY_1:
gui.editor_window.handle_insert_move_mode_button_press()
gui.editor_window.set_mode_selector_to_mode()
return True
if event.keyval == Gdk.KEY_2:
gui.editor_window.handle_over_move_mode_button_press()
gui.editor_window.set_mode_selector_to_mode()
return True
if event.keyval == Gdk.KEY_3:
gui.editor_window.handle_one_roll_mode_button_press()
gui.editor_window.set_mode_selector_to_mode()
return True
if event.keyval == Gdk.KEY_4:
gui.editor_window.handle_two_roll_mode_button_press()
gui.editor_window.set_mode_selector_to_mode()
return True
if event.keyval == Gdk.KEY_5:
gui.editor_window.handle_slide_mode_button_press()
gui.editor_window.set_mode_selector_to_mode()
return True
if event.keyval == Gdk.KEY_6:
gui.editor_window.handle_multi_mode_button_press()
gui.editor_window.set_mode_selector_to_mode()
return True
if event.keyval == Gdk.KEY_7:
gui.editor_window.handle_box_mode_button_press()
gui.editor_window.set_mode_selector_to_mode()
return True
return False
def _handle_clip_key_event(event):
# Key bindings for MOVE MODES
if editorstate.current_is_move_mode():
# LEFT ARROW, prev frame
if event.keyval == Gdk.KEY_Left:
if (event.get_state() & Gdk.ModifierType.CONTROL_MASK):
PLAYER().seek_delta(-10)
else:
PLAYER().seek_delta(-1)
return True
# RIGHT ARROW, next frame
if event.keyval == Gdk.KEY_Right:
if (event.get_state() & Gdk.ModifierType.CONTROL_MASK):
PLAYER().seek_delta(10)
else:
PLAYER().seek_delta(1)
return True
# UP ARROW
if event.keyval == Gdk.KEY_Up:
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
# DOWN ARROW, prev cut
if event.keyval == Gdk.KEY_Down:
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
# SPACE
if event.keyval == Gdk.KEY_space:
if PLAYER().is_playing():
monitorevent.stop_pressed()
else:
monitorevent.play_pressed()
# I
if event.keyval == Gdk.KEY_i:
if (event.get_state() & Gdk.ModifierType.MOD1_MASK):
monitorevent.to_mark_in_pressed()
return True
monitorevent.mark_in_pressed()
return True
if event.keyval == Gdk.KEY_I:
if (event.get_state() & Gdk.ModifierType.MOD1_MASK):
monitorevent.to_mark_in_pressed()
return True
monitorevent.to_mark_in_pressed()
return True
# O
if event.keyval == Gdk.KEY_o:
if (event.get_state() & Gdk.ModifierType.MOD1_MASK):
monitorevent.to_mark_out_pressed()
return True
monitorevent.mark_out_pressed()
return True
if event.keyval == Gdk.KEY_O:
if (event.get_state() & Gdk.ModifierType.MOD1_MASK):
monitorevent.to_mark_out_pressed()
return True
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:
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))
return True
if event.keyval == Gdk.KEY_plus:
pass # not impl
if event.keyval == Gdk.KEY_space:
if PLAYER().is_playing():
monitorevent.stop_pressed()
else:
monitorevent.play_pressed()
return True
return False
def _handle_effects_editor_keys(event):
focus_editor = _get_focus_keyframe_editor(clipeffectseditor.keyframe_editor_widgets)
if focus_editor != None:
if event.keyval == Gdk.KEY_space:
if PLAYER().is_playing():
monitorevent.stop_pressed()
else:
monitorevent.play_pressed()
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()
flowblade-1.12/flowblade-trunk/Flowblade/keyframeeditor.py 0000664 0000000 0000000 00000235625 13062777160 0024005 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
import copy
import math
from gi.repository import Gtk, Gdk, GObject
from gi.repository import Pango, GdkPixbuf
import cairoarea
from editorstate import PLAYER
from editorstate import current_sequence
import gui
import guicomponents
import guiutils
import propertyedit
import propertyparse
import respaths
import utils
import viewgeom
# Draw consts
CLIP_EDITOR_WIDTH = 250
CLIP_EDITOR_HEIGHT = 21
END_PAD = 18
TOP_PAD = 2
BUTTON_WIDTH = 26
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
EP_HALF = 4
GEOMETRY_EDITOR_WIDTH = 250
GEOMETRY_EDITOR_HEIGHT = 200
GEOM_EDITOR_SIZE_LARGE = 0.9
GEOM_EDITOR_SIZE_SMALL = 0.3
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]
# 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
# Hit values for rotating geom edits, NO_HIT used too
POS_EDIT_HIT = 0
# Colors
POINTER_COLOR = (1, 0.3, 0.3)
CLIP_EDITOR_BG_COLOR = (0.7, 0.7, 0.7)
LIGHT_MULTILPLIER = 1.14
DARK_MULTIPLIER = 0.74
EDITABLE_RECT_COLOR = (0,0,0)
NOT_EDITABLE_RECT_COLOR = (1,0,0)
# 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
actions_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 __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 # -1 added to get correct results, yeah...
# 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.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
# 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)
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(0.4, 0.4, 0.4)
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 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 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 _press_event(self, event):
"""
Mouse button callback
"""
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_length - 1
self.widget.queue_draw()
def _motion_notify_event(self, x, y, state):
"""
Mouse move callback
"""
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:
if abs(lx - self.drag_start_x) < KF_DRAG_THRESHOLD:
return
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()
def _release_event(self, event):
"""
Mouse release callback.
"""
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:
if abs(lx - self.drag_start_x) < KF_DRAG_THRESHOLD:
return
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
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_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 add_keyframe(self, frame):
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):
print "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
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
self._set_pos_to_active_kf()
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()
self.parent_editor.update_slider_value_display(self.current_clip_frame)
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))
# -------------------------------------------------------------- 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.iteritems():
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.iteritems():
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 AbstractScreenEditor:
"""
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 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 = 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):
#print 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))
# ---------------------------------------------------- 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
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
#elif state & Gdk.ModifierType.CONTROL_MASK:
# print "control"
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):
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()
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 BoxGeometryScreenEditor(AbstractScreenEditor):
"""
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):
AbstractScreenEditor.__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 immediatyly 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 print_keyframes(self):
for i in range(0, len(self.keyframes)):
print self.keyframes[i]
class RotatingScreenEditor(AbstractScreenEditor):
"""
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):
AbstractScreenEditor.__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
def create_edit_points_and_values(self):
# creates untransformed edit shape to init array, values will 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
# --------------------------------------------------------- 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):
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()
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()
# ----------------------------------------------------------- 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 connect 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):
GObject.GObject.__init__(self)
self.set_homogeneous(False)
self.set_spacing(2)
# Buttons
self.add_button = guiutils.get_image_button("add_kf.png", BUTTON_WIDTH, BUTTON_HEIGHT)
self.delete_button = guiutils.get_image_button("delete_kf.png", BUTTON_WIDTH, BUTTON_HEIGHT)
self.prev_kf_button = guiutils.get_image_button("prev_kf.png", BUTTON_WIDTH, BUTTON_HEIGHT)
self.next_kf_button = guiutils.get_image_button("next_kf.png", BUTTON_WIDTH, BUTTON_HEIGHT)
self.prev_frame_button = guiutils.get_image_button("kf_edit_prev_frame.png", BUTTON_WIDTH, BUTTON_HEIGHT)
self.next_frame_button = guiutils.get_image_button("kf_edit_next_frame.png", 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)
# 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.modify_font(Pango.FontDescription("light 8"))
self.kf_info_label.set_text("1/1")
# Build row
self.pack_start(self.add_button, False, False, 0)
self.pack_start(self.delete_button, 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.prev_frame_button, False, False, 0)
self.pack_start(self.next_frame_button, False, False, 0)
self.pack_start(guiutils.pad_label(4,4), False, False, 0)
self.pack_start(self.kf_info_label, False, False, 0)
self.pack_start(Gtk.Label(), True, True, 0)
self.pack_start(self.kf_pos_label, False, False, 0)
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))
class GeometryEditorButtonsRow(Gtk.HBox):
def __init__(self, editor_parent):
"""
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:"))
surface = cairo.ImageSurface.create_from_png(respaths.IMAGE_PATH + "geom_action.png")
action_menu_button = guicomponents.PressLaunch(self._show_actions_menu, surface, 24, 22)
size_select = Gtk.ComboBoxText()
size_select.append_text(_("Large"))
size_select.append_text(_("Medium"))
size_select.append_text(_("Small"))
size_select.set_active(1)
size_select.set_size_request(120, 30)
font_desc = Pango.FontDescription("normal 9")
size_select.get_child().modify_font(font_desc)
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(guiutils.get_pad_label(2, 10), False, False, 0)
self.pack_start(name_label, False, False, 0)
self.pack_start(size_select, False, False, 0)
self.pack_start(Gtk.Label(), True, True, 0)
self.pack_start(action_menu_button.widget, False, False, 0)
self.pack_start(guiutils.get_pad_label(2, 10), 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):
"""
Extending editor is parent editor for ClipKeyFrameEditor and is updated
from timeline posion changes.
Extending editor also has slider for setting keyframe values.
"""
def __init__(self, editable_property, use_clip_in=True):
# 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)
# 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 = guiutils.get_slider_row(editable_property, self.slider_value_changed)
self.value_slider_row = row
self.slider = slider
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
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 "update_editor_view 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):
AbstractKeyFrameEditor.__init__(self, editable_property, use_clip_in)
# 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)
self.buttons_row = ClipEditorButtonsRow(self)
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)
self.active_keyframe_changed() # to do update gui to current values
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 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 pos_entry_enter_hit(self, entry):
val = entry.get_text() #error handl?
self.clip_editor.active_kf_pos_entered(int(val))
self.update_editor_view()
self.update_property_value()
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 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.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)
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 = BoxGeometryScreenEditor(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)
# 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.
keyframes = []
for kf in self.geom_kf_edit.keyframes:
frame, rect, opacity = kf
clip_kf = (frame, opacity)
keyframes.append(clip_kf)
self.clip_editor.keyframes = 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.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)
self.active_keyframe_changed() # to do update gui to current values
self.queue_draw()
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.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.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())
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())
def slider_value_changed(self, adjustment):
value = adjustment.get_value()
self.clip_editor.set_active_kf_value(value)
self.update_property_value()
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())
def arrow_edit(self, keyval, CTRL_DOWN):
if CTRL_DOWN:
delta = 10
else:
delta = 1
self.geom_kf_edit.handle_arrow_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)
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())
def _reset_rect_pressed(self):
self.geom_kf_edit.reset_active_keyframe_shape(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.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.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.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 = RotatingScreenEditor(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 rotating_ge_write_out_keyframes(ep, keyframes):
x_val = ""
y_val = ""
x_scale_val = ""
y_scale_val = ""
rotation_val = ""
opacity_val = ""
for kf in keyframes:
frame, transf, opacity = kf
x, y, x_scale, y_scale, rotation = transf
x_val += str(frame) + "=" + str(propertyparse.get_frei0r_cairo_position(x, ep.profile_width)) + ";"
y_val += str(frame) + "=" + str(propertyparse.get_frei0r_cairo_position(y, ep.profile_height)) + ";"
x_scale_val += str(frame) + "=" + str(propertyparse.get_frei0r_cairo_scale(x_scale)) + ";"
y_scale_val += str(frame) + "=" + str(propertyparse.get_frei0r_cairo_scale(y_scale)) + ";"
rotation_val += str(frame) + "=" + str(rotation / 360.0) + ";"
opacity_val += str(frame) + "=" + str(opacity / 100.0) + ";"
x_val = x_val.strip(";")
y_val = y_val.strip(";")
x_scale_val = x_scale_val.strip(";")
y_scale_val = y_scale_val.strip(";")
rotation_val = rotation_val.strip(";")
opacity_val = opacity_val.strip(";")
ep.x.write_value(x_val)
ep.y.write_value(y_val)
ep.x_scale.write_value(x_scale_val)
ep.y_scale.write_value(y_scale_val)
ep.rotation.write_value(rotation_val)
ep.opacity.write_value(opacity_val)
# ----------------------------------------------------------------- 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-1.12/flowblade-trunk/Flowblade/launch/ 0000775 0000000 0000000 00000000000 13062777160 0021656 5 ustar 00root root 0000000 0000000 flowblade-1.12/flowblade-trunk/Flowblade/launch/flowbladeaudiorender 0000664 0000000 0000000 00000000470 13062777160 0025763 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
import sys
import os
modules_path = os.path.dirname(os.path.abspath(sys.argv[0])).rstrip("/launch")
sys.path.insert(0, modules_path)
sys.path.insert(0, modules_path + "/vieweditor")
sys.path.insert(0, modules_path + "/tools")
import audiowaveformrenderer
audiowaveformrenderer.main()
flowblade-1.12/flowblade-trunk/Flowblade/launch/flowbladebatch 0000775 0000000 0000000 00000000466 13062777160 0024553 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
import sys
import os
modules_path = os.path.dirname(os.path.abspath(sys.argv[0])).rstrip("/launch")
sys.path.insert(0, modules_path)
sys.path.insert(0, modules_path + "/vieweditor")
sys.path.insert(0, modules_path + "/tools")
import batchrendering
batchrendering.main(modules_path)
flowblade-1.12/flowblade-trunk/Flowblade/launch/flowbladegmic 0000775 0000000 0000000 00000001407 13062777160 0024405 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
import sys
import os
modules_path = os.path.dirname(os.path.abspath(sys.argv[0])).rstrip("/launch")
sys.path.insert(0, modules_path)
sys.path.insert(0, modules_path + "/vieweditor")
sys.path.insert(0, modules_path + "/tools")
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, 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-1.12/flowblade-trunk/Flowblade/launch/flowblademedialinker 0000775 0000000 0000000 00000001563 13062777160 0025755 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
import sys
import os
modules_path = os.path.dirname(os.path.abspath(sys.argv[0])).rstrip("/launch")
sys.path.insert(0, modules_path)
sys.path.insert(0, modules_path + "/vieweditor")
sys.path.insert(0, modules_path + "/tools")
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, 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-1.12/flowblade-trunk/Flowblade/launch/flowbladephantom 0000775 0000000 0000000 00000000214 13062777160 0025127 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-1.12/flowblade-trunk/Flowblade/launch/flowbladesinglerender 0000664 0000000 0000000 00000000504 13062777160 0026141 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
import sys
import os
modules_path = os.path.dirname(os.path.abspath(sys.argv[0])).rstrip("/launch")
sys.path.insert(0, modules_path)
sys.path.insert(0, modules_path + "/vieweditor")
sys.path.insert(0, modules_path + "/tools")
import batchrendering
batchrendering.single_render_main(modules_path)
flowblade-1.12/flowblade-trunk/Flowblade/launch/natron_clip_export_start.sh 0000775 0000000 0000000 00000000060 13062777160 0027337 0 ustar 00root root 0000000 0000000 #!/bin/bash
Natron $1"natronclipimportinit.py"
flowblade-1.12/flowblade-trunk/Flowblade/launch/natronclipimportinit.py 0000664 0000000 0000000 00000005037 13062777160 0026525 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 .
"""
"""
NOTE: THIS SCRIPT IS RUN BY NATRON WHEN LAUNCHING IT AND HAS NO ACCES TO
OTHER PYTHON MODULES IN FLOWBLADE.
"""
import os
import NatronEngine
def createInstance(app,group):
# Get export data
natron_dir = get_hidden_user_dir_path() + "natron"
exportfile = get_latest_clip_export_file(natron_dir)
clip_path, mark_in, mark_out = get_export_data(exportfile)
# Create Natron graph
readerNode = app.createReader("")
viewerNode = app.createNode("fr.inria.built-in.Viewer")
viewerNode.connectInput(0, readerNode)
reader = app.Read1
reader.filename.set(clip_path)
reader.getParam("firstFrame").set(int(mark_in))
reader.getParam("lastFrame").set(int(mark_out) + 1)
reader.getParam("startingTime").set(0)
readerNode.setPosition(300.0, 100.0)
viewerNode.setPosition(315.0, 300.0)
app.getProjectParam("frameRange").set(1, int(mark_out) - int(mark_in) + 1)
# ---------------------------------------------------- helper funcs
def get_hidden_user_dir_path():
return os.getenv("HOME") + "/.flowblade/"
def get_latest_clip_export_file(dirpath):
from os import listdir
from os.path import isfile, join
file_paths = [f for f in listdir(dirpath) if isfile(join(dirpath, f))]
# Get files staring with "clipexport_"
clip_export_files = []
for fpath in file_paths:
if fpath.startswith("clipexport_"):
clip_export_files.append(dirpath + "/" + fpath)
newest = max(clip_export_files, key=os.path.getctime)
return newest
def get_export_data(export_file):
data_file = open(export_file)
data_text = data_file.read()
tokens = data_text.split(" ")
return (tokens[0], tokens[1], tokens[2])
flowblade-1.12/flowblade-trunk/Flowblade/locale/ 0000775 0000000 0000000 00000000000 13062777160 0021643 5 ustar 00root root 0000000 0000000 flowblade-1.12/flowblade-trunk/Flowblade/locale/Flowblade/ 0000775 0000000 0000000 00000000000 13062777160 0023542 5 ustar 00root root 0000000 0000000 flowblade-1.12/flowblade-trunk/Flowblade/locale/Flowblade/flowblade.pot 0000664 0000000 0000000 00000266676 13062777160 0026254 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: 2017-03-17 17:18+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:761
msgid "Too small screen for this application."
msgstr ""
#: app.py:764
msgid "Minimum screen dimensions for this application are 1152 x 768.\n"
msgstr ""
#: app.py:765
msgid "Your screen dimensions are "
msgstr ""
#: app.py:798 projectaction.py:365 projectaction.py:695
msgid "Project has not been saved previously"
msgstr ""
#: app.py:799 projectaction.py:366 projectaction.py:696
msgid "Save project with File -> Save As before closing."
msgstr ""
#: projectaction.py:111
msgid "Media asset was missing!"
msgstr ""
#: projectaction.py:112
msgid "Path of missing asset:"
msgstr ""
#: projectaction.py:113
msgid ""
"Relative search for replacement file in sub folders of project file failed."
msgstr ""
#: projectaction.py:114
msgid "To load the project you will need to either:"
msgstr ""
#: projectaction.py:115
msgid ""
"Open project in 'Media Relinker' tool to relink media assets to new files, or"
msgstr ""
#: projectaction.py:116
msgid "Place a file with the same exact name and path on the hard drive"
msgstr ""
#: projectaction.py:117
msgid "Open project in Media Relinker tool"
msgstr ""
#: projectaction.py:136
msgid "Profile with Description: '"
msgstr ""
#: projectaction.py:136
msgid "' was not found on load!"
msgstr ""
#: projectaction.py:137
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:138
msgid "User Profiles can be created by selecting 'Edit->Profiles Manager'."
msgstr ""
#: projectaction.py:145
msgid "Opening"
msgstr ""
#: projectaction.py:275
msgid "Media files already present in project were opened!"
msgstr ""
#: projectaction.py:281
msgid ""
"Files already present:\n"
"\n"
msgstr ""
#: projectaction.py:480
msgid "Selected folder contains files"
msgstr ""
#: projectaction.py:481
msgid ""
"When saving a back-up snapshot of the project, the selected folder\n"
"has to be empty."
msgstr ""
#: projectaction.py:552
msgid "Copying project media assets"
msgstr ""
#: projectaction.py:553
msgid "Saving project file"
msgstr ""
#: projectaction.py:708
msgid "Project not found on disk"
msgstr ""
#: projectaction.py:709
msgid "Project can't be loaded."
msgstr ""
#: projectaction.py:717
msgid "Project has not been saved since it was opened."
msgstr ""
#: projectaction.py:722
msgid "Project was saved less than a minute ago."
msgstr ""
#: projectaction.py:725
msgid "Project was saved one minute ago."
msgstr ""
#: projectaction.py:727
msgid "Project was saved "
msgstr ""
#: projectaction.py:727
msgid " minutes ago."
msgstr ""
#: projectaction.py:781
msgid "Render launch failed!"
msgstr ""
#: projectaction.py:782 projectaction.py:796 tools/batchrendering.py:299
msgid "Error message: "
msgstr ""
#: projectaction.py:795
msgid "Adding item to render queue failed!"
msgstr ""
#: projectaction.py:814
msgid "Open.."
msgstr ""
#: projectaction.py:844
msgid "No file was selected"
msgstr ""
#: projectaction.py:844
msgid "Select a numbered file to add an Image Sequence to Project."
msgstr ""
#: projectaction.py:852
msgid "Not a sequence file!"
msgstr ""
#: projectaction.py:852
msgid ""
"Selected file does not have a number part in it,\n"
"so it can't be an image sequence file."
msgstr ""
#: projectaction.py:903
msgid "Can't make home folder thumbnails folder"
msgstr ""
#: projectaction.py:904 dialogs.py:359
msgid "Please create and select some other folder then '"
msgstr ""
#: projectaction.py:905
msgid "' as thumbnails folder"
msgstr ""
#: projectaction.py:1023 projectaction.py:1025 projectaction.py:1034
#: projectaction.py:1042 projectaction.py:1049
msgid "N/A"
msgstr ""
#: projectaction.py:1038 guicomponents.py:1751
msgid "Yes"
msgstr ""
#: projectaction.py:1040 guicomponents.py:1753
msgid "No"
msgstr ""
#: projectaction.py:1111
msgid "Can't remove a non-empty bin"
msgstr ""
#: projectaction.py:1112
msgid "You must remove all files from the bin before deleting it."
msgstr ""
#: projectaction.py:1120
msgid "Can't remove last bin"
msgstr ""
#: projectaction.py:1121
msgid "There must always exist at least one bin."
msgstr ""
#: projectaction.py:1207
msgid "Selected sequence is already being edited"
msgstr ""
#: projectaction.py:1208
msgid ""
"Select another sequence. Press Add -button to create a\n"
"new sequence if needed."
msgstr ""
#: projectaction.py:1218 projectaction.py:1235 projectdata.py:201
msgid "sequence_"
msgstr ""
#: projectaction.py:1268
msgid ""
"Are you sure you want to delete\n"
"sequence '"
msgstr ""
#: projectaction.py:1268
msgid "'?"
msgstr ""
#: projectaction.py:1269
msgid "This operation can not be undone. Sequence will be permanently lost."
msgstr ""
#: projectaction.py:1284
msgid "Can't remove last sequence"
msgstr ""
#: projectaction.py:1285
msgid "There must always exist at least one sequence."
msgstr ""
#: editorwindow.py:159
msgid "_File"
msgstr ""
#: editorwindow.py:160
msgid "_New..."
msgstr ""
#: editorwindow.py:161
msgid "_Open..."
msgstr ""
#: editorwindow.py:162
msgid "Open Recent"
msgstr ""
#: editorwindow.py:163
msgid "_Save"
msgstr ""
#: editorwindow.py:164
msgid "_Save As..."
msgstr ""
#: editorwindow.py:165
msgid "Save Backup Snapshot..."
msgstr ""
#: editorwindow.py:166 dialogs.py:300 dialogs.py:1079
msgid "Export"
msgstr ""
#: editorwindow.py:167
msgid "MLT XML"
msgstr ""
#: editorwindow.py:168
msgid "EDL"
msgstr ""
#: editorwindow.py:169
msgid "Current Frame"
msgstr ""
#: editorwindow.py:170
msgid "_Close"
msgstr ""
#: editorwindow.py:171
msgid "_Quit"
msgstr ""
#: editorwindow.py:172
msgid "_Edit"
msgstr ""
#: editorwindow.py:173
msgid "_Undo"
msgstr ""
#: editorwindow.py:174
msgid "_Redo"
msgstr ""
#: editorwindow.py:175
msgid "Copy"
msgstr ""
#: editorwindow.py:176
msgid "Paste"
msgstr ""
#: editorwindow.py:177
msgid "Paste Filters"
msgstr ""
#: editorwindow.py:178
msgid "Add Monitor Clip"
msgstr ""
#: editorwindow.py:179 dialogs.py:1144
msgid "Append"
msgstr ""
#: editorwindow.py:180 dialogs.py:1143 dialogs.py:1177 guicomponents.py:2237
msgid "Insert"
msgstr ""
#: editorwindow.py:181
msgid "Three Point Overwrite"
msgstr ""
#: editorwindow.py:182
msgid "Range Overwrite"
msgstr ""
#: editorwindow.py:183 dialogs.py:1141
msgid "Cut Clip"
msgstr ""
#: editorwindow.py:184 translations.py:513
msgid "Lift"
msgstr ""
#: editorwindow.py:185 dialogs.py:1142
msgid "Splice Out"
msgstr ""
#: editorwindow.py:186 guicomponents.py:1172 guicomponents.py:1269
msgid "Resync"
msgstr ""
#: editorwindow.py:187
msgid "Set Sync Parent"
msgstr ""
#: editorwindow.py:188
msgid "Add Single Track Transition"
msgstr ""
#: editorwindow.py:189
msgid "Add Single Track Fade"
msgstr ""
#: editorwindow.py:190 guicomponents.py:1195 guicomponents.py:1240
msgid "Clear Filters"
msgstr ""
#: editorwindow.py:191 dialogs.py:1157
msgid "Timeline"
msgstr ""
#: editorwindow.py:192
msgid "All Filters Off"
msgstr ""
#: editorwindow.py:193
msgid "All Filters On"
msgstr ""
#: editorwindow.py:194
msgid "Sync All Compositors"
msgstr ""
#: editorwindow.py:195
msgid "Change Sequence Tracks Count..."
msgstr ""
#: editorwindow.py:196
msgid "Watermark..."
msgstr ""
#: editorwindow.py:197 profilesmanager.py:45
msgid "Profiles Manager"
msgstr ""
#: editorwindow.py:198
msgid "Preferences"
msgstr ""
#: editorwindow.py:199 preferenceswindow.py:58
msgid "View"
msgstr ""
#: editorwindow.py:200
msgid "Fullscreen"
msgstr ""
#: editorwindow.py:201 editorwindow.py:515
msgid "Project"
msgstr ""
#: editorwindow.py:202
msgid "Add Media Clip..."
msgstr ""
#: editorwindow.py:203
msgid "Add Image Sequence..."
msgstr ""
#: editorwindow.py:204
msgid "Create Color Clip..."
msgstr ""
#: editorwindow.py:205
msgid "Create Pattern Producer"
msgstr ""
#: editorwindow.py:206 translations.py:395 patternproducer.py:67
msgid "Noise"
msgstr ""
#: editorwindow.py:207 patternproducer.py:72
msgid "EBU Bars"
msgstr ""
#: editorwindow.py:208 patternproducer.py:81
msgid "Ising"
msgstr ""
#: editorwindow.py:209 patternproducer.py:98
msgid "Color Pulse"
msgstr ""
#: editorwindow.py:210 dialogs.py:1133 dialogs.py:1151
msgid "Log Marked Clip Range"
msgstr ""
#: editorwindow.py:211
msgid "Recreate Media Icons..."
msgstr ""
#: editorwindow.py:212
msgid "Remove Unused Media..."
msgstr ""
#: editorwindow.py:213
msgid "JACK Audio..."
msgstr ""
#: editorwindow.py:214
msgid "Change Project Profile..."
msgstr ""
#: editorwindow.py:215 proxyediting.py:216
msgid "Proxy Manager"
msgstr ""
#: editorwindow.py:216
msgid "Project Info"
msgstr ""
#: editorwindow.py:217 editorwindow.py:516 rendergui.py:172
#: tools/batchrendering.py:865 tools/gmic.py:771
msgid "Render"
msgstr ""
#: editorwindow.py:218
msgid "Add To Batch Render Queue..."
msgstr ""
#: editorwindow.py:219
msgid "Batch Render Queue"
msgstr ""
#: editorwindow.py:220
msgid "Render Timeline"
msgstr ""
#: editorwindow.py:221 dialogs.py:1185
msgid "Tools"
msgstr ""
#: editorwindow.py:222 tools/titler.py:178
msgid "Titler"
msgstr ""
#: editorwindow.py:223
msgid "Audio Mixer"
msgstr ""
#: editorwindow.py:224 tools/gmic.py:831
msgid "G'MIC Effects"
msgstr ""
#: editorwindow.py:225 medialinker.py:178
msgid "Media Relinker"
msgstr ""
#: editorwindow.py:226
msgid "_Help"
msgstr ""
#: editorwindow.py:227
msgid "Contents"
msgstr ""
#: editorwindow.py:228 dialogs.py:514
msgid "Runtime Environment"
msgstr ""
#: editorwindow.py:229 dialogs.py:1118
msgid "Keyboard Shortcuts"
msgstr ""
#: editorwindow.py:230 dialogs.py:403
msgid "About"
msgstr ""
#: editorwindow.py:508
msgid "Media"
msgstr ""
#: editorwindow.py:512
msgid "Range Log"
msgstr ""
#: editorwindow.py:513
msgid "Filters"
msgstr ""
#: editorwindow.py:514
msgid "Compositors"
msgstr ""
#: editorwindow.py:531
msgid ""
"Prev Frame - Arrow Left\n"
"Next Frame - Arrow Right\n"
"Play - Space\n"
"Stop - Space\n"
"Mark In - I\n"
"Mark Out - O\n"
"Clear Marks\n"
"To Mark In\n"
"To Mark Out"
msgstr ""
#: editorwindow.py:760
msgid "Middlebar Layout"
msgstr ""
#: editorwindow.py:763
msgid "Timecode Left"
msgstr ""
#: editorwindow.py:768
msgid "Timecode Center"
msgstr ""
#: editorwindow.py:772
msgid "Components Centered"
msgstr ""
#: editorwindow.py:786
msgid "Tabs Position"
msgstr ""
#: editorwindow.py:789
msgid "Up"
msgstr ""
#: editorwindow.py:793
msgid "Down"
msgstr ""
#: editorwindow.py:808
msgid "Show Monitor Sequence Profile"
msgstr ""
#: editorwindow.py:813
msgid "Show Master Volume Meter"
msgstr ""
#: editorwindow.py:821
msgid "Monitor Playback Interpolation"
msgstr ""
#: editorwindow.py:825
msgid "Nearest Neighbour (fast)"
msgstr ""
#: editorwindow.py:829
msgid "Bilinear (good)"
msgstr ""
#: editorwindow.py:833
msgid "Bicubic (better)"
msgstr ""
#: editorwindow.py:838
msgid "Hyper/Lanczos (best)"
msgstr ""
#: editorwindow.py:848
msgid "Zoom In"
msgstr ""
#: editorwindow.py:851
msgid "Zoom Out"
msgstr ""
#: editorwindow.py:854
msgid "Zoom Fit"
msgstr ""
#: editorwindow.py:971
msgid "Timeline current frame timecode"
msgstr ""
#: editorwindow.py:973
msgid "Select view mode: Video/Vectorscope/RGBParade"
msgstr ""
#: editorwindow.py:975
msgid "Monitor Sequence/Media current frame timecode"
msgstr ""
#: editorwindow.py:976
msgid "Current Monitor Sequence/Media name"
msgstr ""
#: editorwindow.py:978
msgid "Monitor Sequence/Media current position"
msgstr ""
#: editorwindow.py:980
msgid "Display Current Sequence on Timeline"
msgstr ""
#: editorwindow.py:981
msgid "Display Monitor Clip"
msgstr ""
#: clipeffectseditor.py:122
msgid "Select Filter Group"
msgstr ""
#: clipeffectseditor.py:123
msgid "Current group Filters"
msgstr ""
#: clipeffectseditor.py:223
msgid "Quit editing Clip in editor"
msgstr ""
#: clipeffectseditor.py:236 panels.py:51 panels.py:96 panels.py:114
#: translations.py:563 mlttransitions.py:134 tools/titler.py:198
msgid "Add"
msgstr ""
#: clipeffectseditor.py:237 compositeeditor.py:59 guicomponents.py:1250
#: guicomponents.py:1312 guicomponents.py:1575 guicomponents.py:1624
#: panels.py:52 panels.py:97 panels.py:115 tools/titler.py:199
#: tools/batchrendering.py:1030
msgid "Delete"
msgstr ""
#: clipeffectseditor.py:249
msgid "Clip being edited"
msgstr ""
#: clipeffectseditor.py:250
msgid "Clip Filter Stack"
msgstr ""
#: clipeffectseditor.py:251
msgid "Add Filter to Clip Filter Stack"
msgstr ""
#: clipeffectseditor.py:252
msgid "Delete Filter from Clip Filter Stack"
msgstr ""
#: clipeffectseditor.py:253
msgid "Toggle all Filters On/Off"
msgstr ""
#: clipeffectseditor.py:523
msgid "No editable parameters"
msgstr ""
#: compositeeditor.py:61 render.py:171
msgid "Reset"
msgstr ""
#: compositeeditor.py:65 compositeeditor.py:147
msgid "No Compositor"
msgstr ""
#: compositeeditor.py:169
msgid "Destination Track:"
msgstr ""
#: dialogs.py:53
msgid "New Project"
msgstr ""
#: dialogs.py:55 dialogs.py:110 dialogs.py:168 dialogs.py:221 dialogs.py:261
#: dialogs.py:278 dialogs.py:299 dialogs.py:313 dialogs.py:324 dialogs.py:340
#: dialogs.py:371 dialogs.py:393 dialogs.py:754 dialogs.py:849 dialogs.py:881
#: dialogs.py:922 dialogs.py:950 dialogs.py:978 dialogs.py:1039 dialogs.py:1078
#: dialogs.py:1092 dialogs.py:1106 dialogs.py:1285 propertyeditorbuilder.py:403
#: propertyeditorbuilder.py:473 rendergui.py:48 rendergui.py:103
#: rendergui.py:117 rendergui.py:134 rendergui.py:905 preferenceswindow.py:45
#: tools/batchrendering.py:962 proxyediting.py:429 patternproducer.py:319
#: patternproducer.py:352 patternproducer.py:383 tools/gmic.py:357
#: tools/gmic.py:381 tools/gmic.py:511
msgid "Cancel"
msgstr ""
#: dialogs.py:56 dialogs.py:222 dialogs.py:262 dialogs.py:405 dialogs.py:516
#: dialogs.py:635 dialogs.py:648 dialogs.py:1286 propertyeditorbuilder.py:404
#: propertyeditorbuilder.py:474 rendergui.py:104 preferenceswindow.py:46
#: tools/batchrendering.py:394 tools/gmic.py:382
msgid "OK"
msgstr ""
#: dialogs.py:64 dialogs.py:121
msgid "Project profile:"
msgstr ""
#: dialogs.py:72 projectinfogui.py:46
msgid "Profile"
msgstr ""
#: dialogs.py:78
msgid "Tracks"
msgstr ""
#: dialogs.py:108 dialogs.py:166
msgid "Change Project Profile"
msgstr ""
#: dialogs.py:111 dialogs.py:169
msgid "Save With Changed Profile"
msgstr ""
#: dialogs.py:113 dialogs.py:171
msgid ""
"Project Profile can only changed by saving a version\n"
"with different profile."
msgstr ""
#: dialogs.py:129
msgid "New Profile"
msgstr ""
#: dialogs.py:131 dialogs.py:186 rendergui.py:517 preferenceswindow.py:109
#: preferenceswindow.py:112 tools/gmic.py:716 tools/toolsencoding.py:40
msgid "Select Folder"
msgstr ""
#: dialogs.py:135 dialogs.py:190 rendergui.py:521 tools/toolsencoding.py:44
msgid "Folder:"
msgstr ""
#: dialogs.py:144 dialogs.py:199
msgid "Project Name:"
msgstr ""
#: dialogs.py:148 dialogs.py:203
msgid "New Project File"
msgstr ""
#: dialogs.py:177 dialogs.py:1368 tools/batchrendering.py:1242
msgid "File:"
msgstr ""
#: dialogs.py:178
msgid "File Best Match Profile:"
msgstr ""
#: dialogs.py:179
msgid "Project Current Profile:"
msgstr ""
#: dialogs.py:205
msgid "Project will be saved with profile: "
msgstr ""
#: dialogs.py:219
msgid "Save Project Backup Snapshot"
msgstr ""
#: dialogs.py:224
msgid "Select Snapshot Project Folder"
msgstr ""
#: dialogs.py:228
msgid "Snapshot Folder:"
msgstr ""
#: dialogs.py:236
msgid "Project File Name:"
msgstr ""
#: dialogs.py:259
msgid "Select Project File"
msgstr ""
#: dialogs.py:266
msgid "Flowblade Projects"
msgstr ""
#: dialogs.py:276
msgid "Save Project As"
msgstr ""
#: dialogs.py:279 dialogs.py:314 dialogs.py:372 dialogs.py:394 rendergui.py:118
#: tools/batchrendering.py:963 tools/gmic.py:358
msgid "Save"
msgstr ""
#: dialogs.py:294
msgid "Export Project as XML to"
msgstr ""
#: dialogs.py:311
msgid "Save Runtime Environment Data"
msgstr ""
#: dialogs.py:325 dialogs.py:341 dialogs.py:1040
msgid "Ok"
msgstr ""
#: dialogs.py:326 dialogs.py:342
msgid "Select Thumbnail Folder"
msgstr ""
#: dialogs.py:358
msgid "Can't make home folder render clips folder"
msgstr ""
#: dialogs.py:360
msgid "' as render clips folder"
msgstr ""
#: dialogs.py:364 dialogs.py:385
msgid "Save project '"
msgstr ""
#: dialogs.py:364
msgid "' before exiting?"
msgstr ""
#: dialogs.py:370 dialogs.py:392
msgid "Don't Save"
msgstr ""
#: dialogs.py:385
msgid "' before closing project?"
msgstr ""
#: dialogs.py:501
msgid "Application"
msgstr ""
#: dialogs.py:502
msgid "Thanks"
msgstr ""
#: dialogs.py:503
msgid "License"
msgstr ""
#: dialogs.py:504
msgid "Developers"
msgstr ""
#: dialogs.py:505
msgid "Translations"
msgstr ""
#: dialogs.py:520
msgid "MLT version: "
msgstr ""
#: dialogs.py:526
msgid "GTK version: "
msgstr ""
#: dialogs.py:528
msgid "Locale: "
msgstr ""
#: dialogs.py:531
msgid "INSTALLATION"
msgstr ""
#: dialogs.py:533
msgid "DEVELOPER VERSION"
msgstr ""
#: dialogs.py:535
msgid "Running from: "
msgstr ""
#: dialogs.py:563
msgid " AVAILABLE"
msgstr ""
#: dialogs.py:565
msgid " NOT AVAILABLE, "
msgstr ""
#: dialogs.py:565
msgid " MISSING"
msgstr ""
#: dialogs.py:571
msgid " FOR FILTER "
msgstr ""
#: dialogs.py:571 dialogs.py:574
msgid " NOT FOUND"
msgstr ""
#: dialogs.py:574
msgid " FOR TRANSITION "
msgstr ""
#: dialogs.py:578 dialogs.py:1134 preferenceswindow.py:56
msgid "General"
msgstr ""
#: dialogs.py:579
msgid "MLT Filters"
msgstr ""
#: dialogs.py:580
msgid "MLT Transitions"
msgstr ""
#: dialogs.py:581
msgid "Missing MLT Services"
msgstr ""
#: dialogs.py:584
msgid "Video Codecs"
msgstr ""
#: dialogs.py:585
msgid "Audio Codecs"
msgstr ""
#: dialogs.py:586
msgid "Formats"
msgstr ""
#: dialogs.py:587
msgid "Render Options"
msgstr ""
#: dialogs.py:633 guicomponents.py:1579
msgid "File Properties"
msgstr ""
#: dialogs.py:646
msgid "Clip Properties"
msgstr ""
#: dialogs.py:667
msgid "Loading project"
msgstr ""
#: dialogs.py:701
msgid "Recreating icons"
msgstr ""
#: dialogs.py:704
msgid "Update media lengths data"
msgstr ""
#: dialogs.py:743
msgid "Are you sure you want to delete these media files?"
msgstr ""
#: dialogs.py:744
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:745
msgid ""
"Deleting these files could prevent converting between\n"
"using proxy files and using original media.\n"
"\n"
msgstr ""
#: dialogs.py:755
msgid "Force Delete"
msgstr ""
#: dialogs.py:766
msgid "Open last autosave?"
msgstr ""
#: dialogs.py:767
msgid ""
"It seems that Flowblade exited abnormally last time.\n"
"\n"
msgstr ""
#: dialogs.py:768
msgid ""
"If there is another instance of Flowblade running,\n"
"this dialog has probably detected its autosave file.\n"
"\n"
msgstr ""
#: dialogs.py:769
msgid "It is NOT possible to open this autosaved version later."
msgstr ""
#: dialogs.py:777 dialogs.py:821
msgid "Continue with default 'untitled' project"
msgstr ""
#: dialogs.py:778
msgid "Open Autosaved Project"
msgstr ""
#: dialogs.py:788
msgid "Open a autosave file?"
msgstr ""
#: dialogs.py:789
msgid ""
"There are multiple autosave files from application crashes.\n"
"\n"
msgstr ""
#: dialogs.py:790
msgid ""
"If you just experienced a crash, select the last created autosave "
"file\n"
"to continue working.\n"
"\n"
msgstr ""
#: dialogs.py:791
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:822
msgid "Open Selected Autosave"
msgstr ""
#: dialogs.py:847
msgid "Change Sequence Tracks Count"
msgstr ""
#: dialogs.py:850
msgid "Change Tracks"
msgstr ""
#: dialogs.py:854
msgid "Please note:\n"
msgstr ""
#: dialogs.py:855
msgid ""
" It is recommended that you save Project before completing this operation\n"
msgstr ""
#: dialogs.py:856
msgid " There is no Undo for this operation\n"
msgstr ""
#: dialogs.py:857
msgid " Current Undo Stack will be destroyed\n"
msgstr ""
#: dialogs.py:858
msgid ""
" All Clips and Compositors on deleted Tracks will be permanently destroyed"
msgstr ""
#: dialogs.py:879
msgid "Create New Sequence"
msgstr ""
#: dialogs.py:882
msgid "Create Sequence"
msgstr ""
#: dialogs.py:889
msgid "Sequence Name:"
msgstr ""
#: dialogs.py:897
msgid "Open For Editing:"
msgstr ""
#: dialogs.py:920
msgid "Rename New Media Object"
msgstr ""
#: dialogs.py:923 dialogs.py:951 dialogs.py:1005 guicomponents.py:1574
msgid "Rename"
msgstr ""
#: dialogs.py:930 dialogs.py:958
msgid "New Name:"
msgstr ""
#: dialogs.py:948 guicomponents.py:1199 guicomponents.py:1293
msgid "Rename Clip"
msgstr ""
#: dialogs.py:976
msgid "New Range Item Group"
msgstr ""
#: dialogs.py:979 patternproducer.py:320 patternproducer.py:353
#: patternproducer.py:384
msgid "Create"
msgstr ""
#: dialogs.py:983
msgid "User Group "
msgstr ""
#: dialogs.py:986 dialogs.py:1006
msgid "New Group Name:"
msgstr ""
#: dialogs.py:1004
msgid "Rename Range Log Item Group"
msgstr ""
#: dialogs.py:1012
msgid "Can't open non-valid media"
msgstr ""
#: dialogs.py:1013
msgid "File: "
msgstr ""
#: dialogs.py:1013
msgid ""
"\n"
"is not a valid media file."
msgstr ""
#: dialogs.py:1017
msgid "New Marker"
msgstr ""
#: dialogs.py:1019 guicomponents.py:2064
msgid "Add Marker"
msgstr ""
#: dialogs.py:1026
msgid "Name for marker at "
msgstr ""
#: dialogs.py:1041
msgid "Add Image Sequence Clip"
msgstr ""
#: dialogs.py:1047
msgid "Select First Frame"
msgstr ""
#: dialogs.py:1057
msgid "First frame:"
msgstr ""
#: dialogs.py:1061
msgid "Frames per Source Image:"
msgstr ""
#: dialogs.py:1076
msgid "Export EDL"
msgstr ""
#: dialogs.py:1090
msgid "Add Transition"
msgstr ""
#: dialogs.py:1093 dialogs.py:1107
msgid "Apply"
msgstr ""
#: dialogs.py:1104
msgid "Add Fade"
msgstr ""
#: dialogs.py:1121 dialogs.py:1238 tools/titler.py:416 proxyediting.py:420
#: medialinker.py:154 tools/gmic.py:802 tools/gmic.py:893
msgid "Close"
msgstr ""
#: dialogs.py:1124
msgid "Control + N"
msgstr ""
#: dialogs.py:1124
msgid "Create New Project"
msgstr ""
#: dialogs.py:1125
msgid "Control + S"
msgstr ""
#: dialogs.py:1125
msgid "Save Project"
msgstr ""
#: dialogs.py:1126 dialogs.py:1142
msgid "DELETE"
msgstr ""
#: dialogs.py:1126
msgid "Delete Selected Item"
msgstr ""
#: dialogs.py:1127
msgid "ESCAPE"
msgstr ""
#: dialogs.py:1127
msgid "Stop Rendering Audio Levels"
msgstr ""
#: dialogs.py:1128
msgid "Control + Q"
msgstr ""
#: dialogs.py:1128
msgid "Quit"
msgstr ""
#: dialogs.py:1129
msgid "Control + Z"
msgstr ""
#: dialogs.py:1129
msgid "Undo"
msgstr ""
#: dialogs.py:1130
msgid "Control + Y"
msgstr ""
#: dialogs.py:1130
msgid "Redo"
msgstr ""
#: dialogs.py:1131
msgid "Control + O"
msgstr ""
#: dialogs.py:1131
msgid "Open Project"
msgstr ""
#: dialogs.py:1132
msgid "TAB"
msgstr ""
#: dialogs.py:1132
msgid "Switch Monitor Source"
msgstr ""
#: dialogs.py:1133
msgid "Control + L"
msgstr ""
#: dialogs.py:1137
msgid "Set Mark In"
msgstr ""
#: dialogs.py:1138
msgid "Set Mark Out"
msgstr ""
#: dialogs.py:1139
msgid "Alt + I"
msgstr ""
#: dialogs.py:1139
msgid "Go To Mark In"
msgstr ""
#: dialogs.py:1140
msgid "Alt + O"
msgstr ""
#: dialogs.py:1140
msgid "Go To Mark Out"
msgstr ""
#: dialogs.py:1145
msgid "3 Point Overwrite Insert"
msgstr ""
#: dialogs.py:1146
msgid "Add Mark"
msgstr ""
#: dialogs.py:1147
msgid "Control + C"
msgstr ""
#: dialogs.py:1147
msgid "Copy Clips"
msgstr ""
#: dialogs.py:1148
msgid "Control + V"
msgstr ""
#: dialogs.py:1148
msgid "Paste Clips"
msgstr ""
#: dialogs.py:1149 dialogs.py:1184
msgid "R"
msgstr ""
#: dialogs.py:1149 dialogs.py:1184
msgid "Trim Tool Ripple Mode On/Off"
msgstr ""
#: dialogs.py:1150
msgid "S"
msgstr ""
#: dialogs.py:1150
msgid "Resync selected Clip or Compositor"
msgstr ""
#: dialogs.py:1151
msgid "G"
msgstr ""
#: dialogs.py:1152 dialogs.py:1164 dialogs.py:1188
msgid "Left Arrow "
msgstr ""
#: dialogs.py:1152
msgid "Prev Frame Trim Edit"
msgstr ""
#: dialogs.py:1153 dialogs.py:1165 dialogs.py:1189
msgid "Right Arrow"
msgstr ""
#: dialogs.py:1153
msgid "Next Frame Trim Edit"
msgstr ""
#: dialogs.py:1154 dialogs.py:1166
msgid "Control + Left Arrow "
msgstr ""
#: dialogs.py:1154
msgid "Back 10 Frames Trim Edit"
msgstr ""
#: dialogs.py:1155 dialogs.py:1167
msgid "Control + Right Arrow"
msgstr ""
#: dialogs.py:1155
msgid "Forward 10 Frames Trim Edit"
msgstr ""
#: dialogs.py:1156
msgid "ENTER"
msgstr ""
#: dialogs.py:1156
msgid "Complete Keyboard Trim Edit"
msgstr ""
#: dialogs.py:1160
msgid "SPACE"
msgstr ""
#: dialogs.py:1160
msgid "Start / Stop Playback"
msgstr ""
#: dialogs.py:1161
msgid "Backwards Faster"
msgstr ""
#: dialogs.py:1162 proxyediting.py:353 tools/gmic.py:769
msgid "Stop"
msgstr ""
#: dialogs.py:1163
msgid "Forward Faster"
msgstr ""
#: dialogs.py:1164
msgid "Prev Frame"
msgstr ""
#: dialogs.py:1165
msgid "Next Frame"
msgstr ""
#: dialogs.py:1166
msgid "Move Back 10 Frames"
msgstr ""
#: dialogs.py:1167
msgid "Move Forward 10 Frames"
msgstr ""
#: dialogs.py:1168 dialogs.py:1190
msgid "Up Arrow"
msgstr ""
#: dialogs.py:1168
msgid "Next Edit/Mark"
msgstr ""
#: dialogs.py:1169 dialogs.py:1191
msgid "Down Arrow"
msgstr ""
#: dialogs.py:1169
msgid "Prev Edit/Mark"
msgstr ""
#: dialogs.py:1170
msgid "HOME"
msgstr ""
#: dialogs.py:1170
msgid "Go To Start"
msgstr ""
#: dialogs.py:1171
msgid "END"
msgstr ""
#: dialogs.py:1171
msgid "Go To End"
msgstr ""
#: dialogs.py:1172
msgid "Shift + I"
msgstr ""
#: dialogs.py:1172
msgid "To Mark In"
msgstr ""
#: dialogs.py:1173
msgid "Shift + O"
msgstr ""
#: dialogs.py:1173
msgid "To Mark Out"
msgstr ""
#: dialogs.py:1174
msgid "Playback"
msgstr ""
#: dialogs.py:1178 guicomponents.py:2243 translations.py:560
msgid "Overwrite"
msgstr ""
#: dialogs.py:1179 guicomponents.py:2249
msgid "Trim"
msgstr ""
#: dialogs.py:1180 guicomponents.py:2255
msgid "Roll"
msgstr ""
#: dialogs.py:1181 guicomponents.py:2261
msgid "Slip"
msgstr ""
#: dialogs.py:1182 guicomponents.py:2267
msgid "Spacer"
msgstr ""
#: dialogs.py:1183 guicomponents.py:2273 translations.py:544
msgid "Box"
msgstr ""
#: dialogs.py:1188
msgid "Move Source Video Left 1px"
msgstr ""
#: dialogs.py:1189
msgid "Move Source Video Right 1px"
msgstr ""
#: dialogs.py:1190
msgid "Move Source Video Up 1px"
msgstr ""
#: dialogs.py:1191
msgid "Move Source Video Down 1px"
msgstr ""
#: dialogs.py:1192
msgid "Control + Arrow"
msgstr ""
#: dialogs.py:1192
msgid "Move Source Video 10px"
msgstr ""
#: dialogs.py:1193
msgid "Control + Mouse Drag"
msgstr ""
#: dialogs.py:1193
msgid "Keep Aspect Ratio in Affine Blend scaling"
msgstr ""
#: dialogs.py:1194
msgid "Shift"
msgstr ""
#: dialogs.py:1194
msgid "Snap to X or Y of drag start point"
msgstr ""
#: dialogs.py:1195
msgid "Geometry Editor"
msgstr ""
#: dialogs.py:1236
msgid "Sequence Watermark"
msgstr ""
#: dialogs.py:1240
msgid "Sequence:"
msgstr ""
#: dialogs.py:1244
msgid "Watermark:"
msgstr ""
#: dialogs.py:1246
msgid "Set Watermark File"
msgstr ""
#: dialogs.py:1247
msgid "Remove Watermark"
msgstr ""
#: dialogs.py:1283
msgid "Select Watermark File"
msgstr ""
#: dialogs.py:1312
msgid "All files"
msgstr ""
#: dialogs.py:1334
msgid "Saving project snapshot"
msgstr ""
#: dialogs.py:1356
msgid "Loaded Media Profile Mismatch"
msgstr ""
#: dialogs.py:1358
msgid "Keep Current Profile"
msgstr ""
#: dialogs.py:1359
msgid "Change To File Profile"
msgstr ""
#: dialogs.py:1361
msgid "A video file was loaded that does not match the Project Profile!"
msgstr ""
#: dialogs.py:1369
msgid "File Profile:"
msgstr ""
#: dialogs.py:1370
msgid "Project Profile:"
msgstr ""
#: dialogs.py:1371
msgid ""
"Using a matching profile is recommended.\n"
"\n"
"This message is only displayed on first media load for Project."
msgstr ""
#: editevent.py:226
msgid "Can't put an audio clip on a video track."
msgstr ""
#: editevent.py:227 editevent.py:839 movemodes.py:607
msgid "Track "
msgstr ""
#: editevent.py:227
msgid " is a video track and can't display audio only material."
msgstr ""
#: editevent.py:838
msgid "Can't edit a locked track"
msgstr ""
#: editevent.py:839
msgid " is locked. Unlock track to edit it."
msgstr ""
#: editorpersistance.py:156
msgid "Empty"
msgstr ""
#: guicomponents.py:283
msgid "active"
msgstr ""
#: guicomponents.py:560
msgid "default"
msgstr ""
#: guicomponents.py:589
msgid "Clip:"
msgstr ""
#: guicomponents.py:594
msgid "Track:"
msgstr ""
#: guicomponents.py:597
msgid "Pos:"
msgstr ""
#: guicomponents.py:624
msgid "Clip: "
msgstr ""
#: guicomponents.py:626
msgid "Track: "
msgstr ""
#: guicomponents.py:628 guicomponents.py:639 guicomponents.py:721
msgid "Position:"
msgstr ""
#: guicomponents.py:635
msgid "Clip:"
msgstr ""
#: guicomponents.py:637
msgid "Track:"
msgstr ""
#: guicomponents.py:715
msgid "Source Track:"
msgstr ""
#: guicomponents.py:718
msgid "Destination Track:"
msgstr ""
#: guicomponents.py:724
msgid "Length:"
msgstr ""
#: guicomponents.py:1092 guicomponents.py:1096
msgid "Lock Track"
msgstr ""
#: guicomponents.py:1093 guicomponents.py:1097
msgid "Unlock Track"
msgstr ""
#: guicomponents.py:1102
msgid "Large Height"
msgstr ""
#: guicomponents.py:1107
msgid "Normal Height"
msgstr ""
#: guicomponents.py:1131 guicomponents.py:1219 guicomponents.py:1261
msgid "Open in Filters Editor"
msgstr ""
#: guicomponents.py:1138 guicomponents.py:1263 guicomponents.py:1577
msgid "Open in Clip Monitor"
msgstr ""
#: guicomponents.py:1149
msgid "Split Audio"
msgstr ""
#: guicomponents.py:1155
msgid "Split Audio Synched"
msgstr ""
#: guicomponents.py:1162 guicomponents.py:1277
msgid "Display Audio Level"
msgstr ""
#: guicomponents.py:1165 guicomponents.py:1280
msgid "Clear Waveform"
msgstr ""
#: guicomponents.py:1173 guicomponents.py:1270
msgid "Clear Sync Relation"
msgstr ""
#: guicomponents.py:1175 guicomponents.py:1272
msgid "Select Sync Parent Clip..."
msgstr ""
#: guicomponents.py:1202 guicomponents.py:1296
msgid "Clip Info"
msgstr ""
#: guicomponents.py:1247
msgid "Strech Prev Clip to Cover"
msgstr ""
#: guicomponents.py:1248
msgid "Strech Next Clip to Cover"
msgstr ""
#: guicomponents.py:1308
msgid "Open In Compositor Editor"
msgstr ""
#: guicomponents.py:1310
msgid "Sync with Origin Clip"
msgstr ""
#: guicomponents.py:1316 guicomponents.py:1337
msgid "Add Filter"
msgstr ""
#: guicomponents.py:1359
msgid "Add Compositor"
msgstr ""
#: guicomponents.py:1382
msgid "Add Blend"
msgstr ""
#: guicomponents.py:1398
msgid "Show Match Frame"
msgstr ""
#: guicomponents.py:1402
msgid "First Frame in Monitor"
msgstr ""
#: guicomponents.py:1407
msgid "Last Frame in Monitor"
msgstr ""
#: guicomponents.py:1414
msgid "First Frame on Timeline"
msgstr ""
#: guicomponents.py:1419
msgid "Last Frame on Timeline"
msgstr ""
#: guicomponents.py:1426 guicomponents.py:2222
msgid "Clear Match Frame"
msgstr ""
#: guicomponents.py:1436
msgid "Select"
msgstr ""
#: guicomponents.py:1440
msgid "All Clips After"
msgstr ""
#: guicomponents.py:1445
msgid "All Clips Before"
msgstr ""
#: guicomponents.py:1455
msgid "Export To Tool"
msgstr ""
#: guicomponents.py:1472
msgid "Clone Filters"
msgstr ""
#: guicomponents.py:1476
msgid "From Next Clip"
msgstr ""
#: guicomponents.py:1481
msgid "From Previous Clip"
msgstr ""
#: guicomponents.py:1490 guicomponents.py:1510
msgid "Mute"
msgstr ""
#: guicomponents.py:1494 guicomponents.py:1514
msgid "Unmute"
msgstr ""
#: guicomponents.py:1500 guicomponents.py:1531
msgid "Mute Audio"
msgstr ""
#: guicomponents.py:1525
msgid "Mute Video"
msgstr ""
#: guicomponents.py:1542
msgid "Mute All"
msgstr ""
#: guicomponents.py:1552
msgid "Clip Color"
msgstr ""
#: guicomponents.py:1554
msgid "Default"
msgstr ""
#: guicomponents.py:1555 translations.py:498 translations.py:570
msgid "Red"
msgstr ""
#: guicomponents.py:1556 translations.py:499 translations.py:565
#: translations.py:571
msgid "Green"
msgstr ""
#: guicomponents.py:1557 translations.py:500 translations.py:566
#: translations.py:572
msgid "Blue"
msgstr ""
#: guicomponents.py:1558
msgid "Orange"
msgstr ""
#: guicomponents.py:1559
msgid "Brown"
msgstr ""
#: guicomponents.py:1560
msgid "Olive"
msgstr ""
#: guicomponents.py:1584 guicomponents.py:1622
msgid "Render Slow/Fast Motion File"
msgstr ""
#: guicomponents.py:1586
msgid "Render Proxy File"
msgstr ""
#: guicomponents.py:1609
msgid "Toggle Active"
msgstr ""
#: guicomponents.py:1610
msgid "Reset Values"
msgstr ""
#: guicomponents.py:1612
msgid "Move Up"
msgstr ""
#: guicomponents.py:1613
msgid "Move Down"
msgstr ""
#: guicomponents.py:1621
msgid "Display In Clip Monitor"
msgstr ""
#: guicomponents.py:1623
msgid "Toggle Star"
msgstr ""
#: guicomponents.py:1631 medialinker.py:134
msgid "Set File Relink Path"
msgstr ""
#: guicomponents.py:1632 medialinker.py:136
msgid "Delete File Relink Path"
msgstr ""
#: guicomponents.py:1634
msgid "Show Full Paths"
msgstr ""
#: guicomponents.py:1700 propertyeditorbuilder.py:508
#: propertyeditorbuilder.py:603 tools/toolsencoding.py:343
msgid "Progressive"
msgstr ""
#: guicomponents.py:1702 tools/toolsencoding.py:345
msgid "Interlaced"
msgstr ""
#: guicomponents.py:1705 tools/toolsencoding.py:348
msgid "Fps: "
msgstr ""
#: guicomponents.py:1708 tools/toolsencoding.py:351
msgid "Pixel Aspect: "
msgstr ""
#: guicomponents.py:1715
msgid "Description:"
msgstr ""
#: guicomponents.py:1717
msgid "Dimensions:"
msgstr ""
#: guicomponents.py:1719
msgid "Frames per second:"
msgstr ""
#: guicomponents.py:1721
msgid "Size:"
msgstr ""
#: guicomponents.py:1723
msgid "Pixel aspect ratio: "
msgstr ""
#: guicomponents.py:1725 profilesmanager.py:138
msgid "Progressive:"
msgstr ""
#: guicomponents.py:1963
msgid "Video:"
msgstr ""
#: guicomponents.py:1968
msgid "Audio:"
msgstr ""
#: guicomponents.py:1973
msgid "Number of Tracks:"
msgstr ""
#: guicomponents.py:2061
msgid "No Markers"
msgstr ""
#: guicomponents.py:2065
msgid "Delete Marker"
msgstr ""
#: guicomponents.py:2067
msgid "Delete All Markers"
msgstr ""
#: guicomponents.py:2075
msgid "Maximize Tracks"
msgstr ""
#: guicomponents.py:2076
msgid "Maximize Video Tracks"
msgstr ""
#: guicomponents.py:2077
msgid "Maximize Audio Tracks"
msgstr ""
#: guicomponents.py:2079
msgid "Minimize Tracks"
msgstr ""
#: guicomponents.py:2081
msgid "Activate All Tracks"
msgstr ""
#: guicomponents.py:2082
msgid "Activate Only Current Top Active Track"
msgstr ""
#: guicomponents.py:2090
msgid "Display Clip Media Thumbnails"
msgstr ""
#: guicomponents.py:2099
msgid "Snapping On"
msgstr ""
#: guicomponents.py:2106
msgid "Show Magnet Icon"
msgstr ""
#: guicomponents.py:2115
msgid "Display All Audio Levels"
msgstr ""
#: guicomponents.py:2118
msgid "Display Audio Levels On Request"
msgstr ""
#: guicomponents.py:2138
msgid "Image"
msgstr ""
#: guicomponents.py:2140
msgid "Vectorscope"
msgstr ""
#: guicomponents.py:2142
msgid "RGB Parade"
msgstr ""
#: guicomponents.py:2146
msgid "Overlay Opacity"
msgstr ""
#: guicomponents.py:2151
msgid "100%"
msgstr ""
#: guicomponents.py:2156
msgid "80%"
msgstr ""
#: guicomponents.py:2161
msgid "50%"
msgstr ""
#: guicomponents.py:2166
msgid "20%"
msgstr ""
#: guicomponents.py:2171
msgid "0%"
msgstr ""
#: guicomponents.py:2191
msgid "Trim View On"
msgstr ""
#: guicomponents.py:2196
msgid "Trim View Single Side Edits Only"
msgstr ""
#: guicomponents.py:2201
msgid "Trim View Off"
msgstr ""
#: guicomponents.py:2217
msgid "Set Current Clip Frame Match Frame"
msgstr ""
#: guicomponents.py:2293
msgid "All Files"
msgstr ""
#: guicomponents.py:2297
msgid "Video Files"
msgstr ""
#: guicomponents.py:2301
msgid "Audio Files"
msgstr ""
#: guicomponents.py:2305
msgid "Graphics Files"
msgstr ""
#: guicomponents.py:2309
msgid "Image Sequences"
msgstr ""
#: guicomponents.py:2313
msgid "Pattern Producers"
msgstr ""
#: guicomponents.py:2326
msgid "2 Columns"
msgstr ""
#: guicomponents.py:2331
msgid "3 Columns"
msgstr ""
#: guicomponents.py:2336
msgid "4 Columns"
msgstr ""
#: guicomponents.py:2341
msgid "5 Columns"
msgstr ""
#: guicomponents.py:2346
msgid "6 Columns"
msgstr ""
#: guicomponents.py:2351
msgid "7 Columns"
msgstr ""
#: movemodes.py:606
msgid "Can't do edit on a locked track"
msgstr ""
#: movemodes.py:607
msgid " is locked. Unlock track to edit it.\n"
msgstr ""
#: panels.py:55
msgid "Add Media File to Bin"
msgstr ""
#: panels.py:56
msgid "Delete Media File from Bin"
msgstr ""
#: panels.py:61
msgid "Render Proxy Files For Selected Media"
msgstr ""
#: panels.py:100
msgid "Add Bin to Project"
msgstr ""
#: panels.py:101
msgid "Delete Bin from Project"
msgstr ""
#: panels.py:110
msgid "Bins"
msgstr ""
#: panels.py:116
msgid "Edit"
msgstr ""
#: panels.py:117
msgid "Add new Sequence to Project"
msgstr ""
#: panels.py:118
msgid "Delete Sequence from Project"
msgstr ""
#: panels.py:119
msgid "Start editing Sequence"
msgstr ""
#: panels.py:133
msgid "Sequences"
msgstr ""
#: panels.py:136
msgid "Select folder for new thumbnails."
msgstr ""
#: panels.py:137
msgid "Old thumbnails in this or other projects will"
msgstr ""
#: panels.py:138
msgid ""
" still be available,\n"
"this only affects thumnails that are created for new media.\n"
msgstr ""
#: panels.py:139
msgid ""
"\n"
"Setting your home folder as thumbnails folder is not allowed."
msgstr ""
#: panels.py:155
msgid "Select folder for rendered clips."
msgstr ""
#: panels.py:156
msgid "Old rendered clips in this or other projects will"
msgstr ""
#: panels.py:157
msgid ""
" still be available,\n"
"this only affects rendered files that are created from now on.\n"
msgstr ""
#: panels.py:158
msgid ""
"\n"
"Setting your home folder as folder for rendered clips is not allowed."
msgstr ""
#: panels.py:242 rendergui.py:531 tools/toolsencoding.py:54
msgid "Name:"
msgstr ""
#: panels.py:243
msgid "Path:"
msgstr ""
#: panels.py:244 panels.py:278
msgid "Image Size:"
msgstr ""
#: panels.py:245 tools/batchrendering.py:1002
msgid "Frames Per Second:"
msgstr ""
#: panels.py:246
msgid "Playtime:"
msgstr ""
#: panels.py:247 panels.py:280
msgid "Video Codec:"
msgstr ""
#: panels.py:248 panels.py:281
msgid "Audio Codec:"
msgstr ""
#: panels.py:249
msgid "Audio Channels:"
msgstr ""
#: panels.py:250
msgid "Audio Sample Rate:"
msgstr ""
#: panels.py:251
msgid "Best Profile:"
msgstr ""
#: panels.py:252
msgid "Matches Project Profile:"
msgstr ""
#: panels.py:275 tools/gmic.py:699
msgid "Mark In:"
msgstr ""
#: panels.py:276 tools/gmic.py:700
msgid "Mark Out:"
msgstr ""
#: panels.py:277
msgid "Clip Length:"
msgstr ""
#: panels.py:279
msgid "Media Path:"
msgstr ""
#: panels.py:309
msgid "Composite clip on:"
msgstr ""
#: panels.py:322 panels.py:415 rendergui.py:544 tools/toolsencoding.py:67
msgid "Type:"
msgstr ""
#: panels.py:331
msgid "Wipe Pattern:"
msgstr ""
#: panels.py:337
msgid "Dip Color:"
msgstr ""
#: panels.py:355 panels.py:428 tools/gmic.py:701
msgid "Length:"
msgstr ""
#: panels.py:361
msgid "First Clip Out Handle:"
msgstr ""
#: panels.py:362 panels.py:365
msgid " frame(s)"
msgstr ""
#: panels.py:364
msgid "Second Clip In Handle:"
msgstr ""
#: panels.py:401 panels.py:455
msgid "Transition Options"
msgstr ""
#: panels.py:402 panels.py:456
msgid "Encoding"
msgstr ""
#: panels.py:403
msgid "Media Overlap info"
msgstr ""
#: panels.py:411 mlttransitions.py:166
msgid "Fade In"
msgstr ""
#: panels.py:412 mlttransitions.py:167
msgid "Fade Out"
msgstr ""
#: panels.py:420
msgid "Color:"
msgstr ""
#: persistance.py:372
msgid "Building sequence "
msgstr ""
#: persistance.py:409
msgid "Loading icons"
msgstr ""
#: projectdata.py:73
msgid "untitled"
msgstr ""
#: projectdata.py:193
msgid "bin_"
msgstr ""
#: projectdata.py:488
msgid "Created using dialog"
msgstr ""
#: projectdata.py:490
msgid "Created using Save As... "
msgstr ""
#: projectdata.py:492
msgid "Saved "
msgstr ""
#: projectdata.py:495
msgid "Saved as "
msgstr ""
#: projectdata.py:497
msgid "Rendered "
msgstr ""
#: projectdata.py:499
msgid "Saved backup snapshot"
msgstr ""
#: render.py:173
msgid "To Queue"
msgstr ""
#: render.py:174
msgid "Save Project in Render Queue"
msgstr ""
#: render.py:177
msgid "Select render range"
msgstr ""
#: render.py:178
msgid "Reset all render options to defaults"
msgstr ""
#: render.py:179
msgid "Begin Rendering"
msgstr ""
#: render.py:204
msgid "Output File: "
msgstr ""
#: render.py:206
msgid "Estimated time left: "
msgstr ""
#: render.py:208
msgid "Render time: "
msgstr ""
#: render.py:225 render.py:251
msgid "Render Time: "
msgstr ""
#: render.py:231 render.py:249
msgid "Estimated Time Left: "
msgstr ""
#: render.py:253
msgid "Render Complete!"
msgstr ""
#: render.py:369
msgid "A File with given path exists!"
msgstr ""
#: render.py:370
msgid ""
"It is not allowed to render Motion Files with same paths as existing files.\n"
"Select another name for file."
msgstr ""
#: render.py:429
msgid "Rendering Motion Clip"
msgstr ""
#: render.py:479
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:111 translations.py:416
msgid "Color"
msgstr ""
#: translations.py:112
msgid "Color Effect"
msgstr ""
#: translations.py:113
msgid "Audio"
msgstr ""
#: translations.py:114
msgid "Audio Filter"
msgstr ""
#: translations.py:115 translations.py:168 translations.py:381
#: translations.py:458
msgid "Blur"
msgstr ""
#: translations.py:116 translations.py:528 propertyeditorbuilder.py:523
#: propertyeditorbuilder.py:618
msgid "Distort"
msgstr ""
#: translations.py:117
msgid "Alpha"
msgstr ""
#: translations.py:118
msgid "Movement"
msgstr ""
#: translations.py:119
msgid "Transform"
msgstr ""
#: translations.py:120 translations.py:477
msgid "Edge"
msgstr ""
#: translations.py:121
msgid "Fix"
msgstr ""
#: translations.py:122
msgid "Artistic"
msgstr ""
#: translations.py:126
msgid "Alpha Gradient"
msgstr ""
#: translations.py:127
msgid "Crop"
msgstr ""
#: translations.py:128
msgid "Alpha Shape"
msgstr ""
#: translations.py:130 translations.py:266
msgid "Volume"
msgstr ""
#: translations.py:131
msgid "Pan"
msgstr ""
#: translations.py:132
msgid "Pan Keyframed"
msgstr ""
#: translations.py:133
msgid "Mono to Stereo"
msgstr ""
#: translations.py:134
msgid "Swap Channels"
msgstr ""
#: translations.py:136
msgid "Pitchshifter"
msgstr ""
#: translations.py:137
msgid "Distort - Barry's Satan"
msgstr ""
#: translations.py:138
msgid "Frequency Shift - Bode/Moog"
msgstr ""
#: translations.py:139
msgid "Equalize - DJ 3-band"
msgstr ""
#: translations.py:140
msgid "Flanger - DJ"
msgstr ""
#: translations.py:141
msgid "Declipper"
msgstr ""
#: translations.py:142
msgid "Delayorama"
msgstr ""
#: translations.py:143
msgid "Distort - Diode Processor"
msgstr ""
#: translations.py:144
msgid "Distort - Foldover"
msgstr ""
#: translations.py:145
msgid "Highpass - Butterworth"
msgstr ""
#: translations.py:146
msgid "Lowpass - Butterworth"
msgstr ""
#: translations.py:147
msgid "GSM Simulator"
msgstr ""
#: translations.py:148
msgid "Reverb - GVerb"
msgstr ""
#: translations.py:149
msgid "Noise Gate"
msgstr ""
#: translations.py:150
msgid "Bandpass"
msgstr ""
#: translations.py:151
msgid "Pitchscaler - High Quality"
msgstr ""
#: translations.py:152
msgid "Equalize - Multiband"
msgstr ""
#: translations.py:153
msgid "Reverb - Plate"
msgstr ""
#: translations.py:154
msgid "Distort - Pointer cast"
msgstr ""
#: translations.py:155
msgid "Rate Shifter"
msgstr ""
#: translations.py:156
msgid "Signal Shifter"
msgstr ""
#: translations.py:157
msgid "Distort - Sinus Wavewrap"
msgstr ""
#: translations.py:158
msgid "Vinyl Effect"
msgstr ""
#: translations.py:159
msgid "Chorus - Multivoice"
msgstr ""
#: translations.py:161
msgid "Charcoal"
msgstr ""
#: translations.py:162
msgid "Glow"
msgstr ""
#: translations.py:163
msgid "Old Film"
msgstr ""
#: translations.py:164
msgid "Scanlines"
msgstr ""
#: translations.py:165
msgid "Cartoon"
msgstr ""
#: translations.py:167
msgid "Pixelize"
msgstr ""
#: translations.py:169
msgid "Grain"
msgstr ""
#: translations.py:171
msgid "Grayscale"
msgstr ""
#: translations.py:172 translations.py:396 translations.py:398
msgid "Contrast"
msgstr ""
#: translations.py:173 translations.py:399 mlttransitions.py:148
msgid "Saturation"
msgstr ""
#: translations.py:174 translations.py:380 translations.py:432
#: translations.py:457 translations.py:531 translations.py:535
msgid "Invert"
msgstr ""
#: translations.py:175 translations.py:400 mlttransitions.py:144
msgid "Hue"
msgstr ""
#: translations.py:176 translations.py:397 translations.py:401
#: translations.py:402
msgid "Brightness"
msgstr ""
#: translations.py:177 translations.py:578
msgid "Sepia"
msgstr ""
#: translations.py:178
msgid "Tint"
msgstr ""
#: translations.py:179
msgid "White Balance"
msgstr ""
#: translations.py:180 translations.py:482
msgid "Levels"
msgstr ""
#: translations.py:182
msgid "Color Clustering"
msgstr ""
#: translations.py:183
msgid "Chroma Hold"
msgstr ""
#: translations.py:184
msgid "Three Layer"
msgstr ""
#: translations.py:185
msgid "Threshold0r"
msgstr ""
#: translations.py:186
msgid "Technicolor"
msgstr ""
#: translations.py:187
msgid "Primaries"
msgstr ""
#: translations.py:188
msgid "Color Distance"
msgstr ""
#: translations.py:189 translations.py:418 translations.py:423
msgid "Threshold"
msgstr ""
#: translations.py:191
msgid "Waves"
msgstr ""
#: translations.py:192
msgid "Lens Correction"
msgstr ""
#: translations.py:193 translations.py:430
msgid "Flip"
msgstr ""
#: translations.py:194
msgid "Mirror"
msgstr ""
#: translations.py:195
msgid "V Sync"
msgstr ""
#: translations.py:197
msgid "Edge Glow"
msgstr ""
#: translations.py:198
msgid "Sobel"
msgstr ""
#: translations.py:200
msgid "Denoise"
msgstr ""
#: translations.py:201 translations.py:505
msgid "Sharpness"
msgstr ""
#: translations.py:202
msgid "Letterbox"
msgstr ""
#: translations.py:204
msgid "Baltan"
msgstr ""
#: translations.py:205
msgid "Vertigo"
msgstr ""
#: translations.py:206
msgid "Nervous"
msgstr ""
#: translations.py:207
msgid "Freeze"
msgstr ""
#: translations.py:209 translations.py:426
msgid "Rotate"
msgstr ""
#: translations.py:210
msgid "Shear"
msgstr ""
#: translations.py:211
msgid "Translate"
msgstr ""
#: translations.py:214
msgid "Color Select"
msgstr ""
#: translations.py:215
msgid "Alpha Modify"
msgstr ""
#: translations.py:216
msgid "Spill Supress"
msgstr ""
#: translations.py:217
msgid "RGB Noise"
msgstr ""
#: translations.py:218
msgid "Box Blur"
msgstr ""
#: translations.py:219
msgid "IRR Blur"
msgstr ""
#: translations.py:220
msgid "Color Halftone"
msgstr ""
#: translations.py:221
msgid "Dither"
msgstr ""
#: translations.py:222
msgid "Vignette"
msgstr ""
#: translations.py:223
msgid "Emboss"
msgstr ""
#: translations.py:224
msgid "3 Point Balance"
msgstr ""
#: translations.py:225
msgid "Colorize"
msgstr ""
#: translations.py:226
msgid "Brightness Keyframed"
msgstr ""
#: translations.py:227
msgid "RGB Adjustment"
msgstr ""
#: translations.py:228
msgid "Color Tap"
msgstr ""
#: translations.py:229
msgid "Posterize"
msgstr ""
#: translations.py:230
msgid "Soft Glow"
msgstr ""
#: translations.py:231
msgid "Newspaper"
msgstr ""
#: translations.py:234
msgid "Luma Key"
msgstr ""
#: translations.py:235
msgid "Chroma Key"
msgstr ""
#: translations.py:236 mlttransitions.py:123
msgid "Affine"
msgstr ""
#: translations.py:237
msgid "Color Adjustment"
msgstr ""
#: translations.py:238
msgid "Color Grading"
msgstr ""
#: translations.py:239
msgid "Curves"
msgstr ""
#: translations.py:240
msgid "Lift Gain Gamma"
msgstr ""
#: translations.py:241
msgid "Image Grid"
msgstr ""
#: translations.py:244
msgid "Color Lift Gain Gamma"
msgstr ""
#: translations.py:250 translations.py:433
msgid "Position"
msgstr ""
#: translations.py:251
msgid "Grad width"
msgstr ""
#: translations.py:252 translations.py:264 translations.py:427
msgid "Tilt"
msgstr ""
#: translations.py:253 translations.py:562
msgid "Min"
msgstr ""
#: translations.py:254 translations.py:561
msgid "Max"
msgstr ""
#: translations.py:255 translations.py:453
msgid "Left"
msgstr ""
#: translations.py:256 translations.py:454
msgid "Right"
msgstr ""
#: translations.py:257 translations.py:455
msgid "Top"
msgstr ""
#: translations.py:258 translations.py:456
msgid "Bottom"
msgstr ""
#: translations.py:259
msgid "Shape"
msgstr ""
#: translations.py:260
msgid "Pos X"
msgstr ""
#: translations.py:261
msgid "Pos Y"
msgstr ""
#: translations.py:262
msgid "Size X"
msgstr ""
#: translations.py:263
msgid "Size Y"
msgstr ""
#: translations.py:265
msgid "Trans. Width"
msgstr ""
#: translations.py:267 translations.py:268
msgid "Left/Right"
msgstr ""
#: translations.py:269 translations.py:272 translations.py:275
#: translations.py:277 translations.py:281 translations.py:285
#: translations.py:286 translations.py:297 translations.py:299
#: translations.py:302 translations.py:305 translations.py:308
#: translations.py:311 translations.py:319 translations.py:327
#: translations.py:331 translations.py:333 translations.py:349
#: translations.py:353 translations.py:356 translations.py:358
#: translations.py:360 translations.py:362 translations.py:368
#: translations.py:375
msgid "Dry/Wet"
msgstr ""
#: translations.py:270
msgid "Pitch Shift"
msgstr ""
#: translations.py:271
msgid "Buffer Size"
msgstr ""
#: translations.py:273
msgid "Decay Time(samples)"
msgstr ""
#: translations.py:274
msgid "Knee Point(dB)"
msgstr ""
#: translations.py:276
msgid "Frequency shift"
msgstr ""
#: translations.py:278
msgid "Low Gain(dB)"
msgstr ""
#: translations.py:279
msgid "Mid Gain(dB)"
msgstr ""
#: translations.py:280
msgid "High Gain(dB)"
msgstr ""
#: translations.py:282
msgid "Oscillation period(s)"
msgstr ""
#: translations.py:283
msgid "Oscillation depth(ms)"
msgstr ""
#: translations.py:284
msgid "Feedback%"
msgstr ""
#: translations.py:287
msgid "Random seed"
msgstr ""
#: translations.py:288
msgid "Input Gain(dB)"
msgstr ""
#: translations.py:289
msgid "Feedback(%)"
msgstr ""
#: translations.py:290
msgid "Number of taps"
msgstr ""
#: translations.py:291
msgid "First Delay(s)"
msgstr ""
#: translations.py:292
msgid "Delay Range(s)"
msgstr ""
#: translations.py:293
msgid "Delay Change"
msgstr ""
#: translations.py:294
msgid "Delay Random(%)"
msgstr ""
#: translations.py:295
msgid "Amplitude Change"
msgstr ""
#: translations.py:296
msgid "Amplitude Random(%)"
msgstr ""
#: translations.py:298 translations.py:361 translations.py:407
#: translations.py:439
msgid "Amount"
msgstr ""
#: translations.py:300
msgid "Drive"
msgstr ""
#: translations.py:301
msgid "Skew"
msgstr ""
#: translations.py:303 translations.py:306
msgid "Cutoff Frequency(Hz)"
msgstr ""
#: translations.py:304 translations.py:307
msgid "Resonance"
msgstr ""
#: translations.py:309
msgid "Passes"
msgstr ""
#: translations.py:310
msgid "Error Rate"
msgstr ""
#: translations.py:312
msgid "Roomsize"
msgstr ""
#: translations.py:313
msgid "Reverb time(s)"
msgstr ""
#: translations.py:314 translations.py:351
msgid "Damping"
msgstr ""
#: translations.py:315
msgid "Input bandwith"
msgstr ""
#: translations.py:316
msgid "Dry signal level(dB)"
msgstr ""
#: translations.py:317
msgid "Early reflection level(dB)"
msgstr ""
#: translations.py:318
msgid "Tail level(dB)"
msgstr ""
#: translations.py:320
msgid "LF keyfilter(Hz)"
msgstr ""
#: translations.py:321
msgid "HF keyfilter(Hz)"
msgstr ""
#: translations.py:322
msgid "Threshold(dB)"
msgstr ""
#: translations.py:323
msgid "Attack(ms)"
msgstr ""
#: translations.py:324
msgid "Hold(ms)"
msgstr ""
#: translations.py:325
msgid "Decay(ms)"
msgstr ""
#: translations.py:326
msgid "Range(dB)"
msgstr ""
#: translations.py:328
msgid "Center Frequency(Hz)"
msgstr ""
#: translations.py:329
msgid "Bandwidth(Hz)"
msgstr ""
#: translations.py:330
msgid "Stages"
msgstr ""
#: translations.py:332
msgid "Pitch-coefficient"
msgstr ""
#: translations.py:334
msgid "50Hz gain"
msgstr ""
#: translations.py:335
msgid "100Hz gain"
msgstr ""
#: translations.py:336
msgid "156Hz gain"
msgstr ""
#: translations.py:337
msgid "220Hz gain"
msgstr ""
#: translations.py:338
msgid "311Hz gain"
msgstr ""
#: translations.py:339
msgid "440Hz gain"
msgstr ""
#: translations.py:340
msgid "622Hz gain"
msgstr ""
#: translations.py:341
msgid "880Hz gain"
msgstr ""
#: translations.py:342
msgid "1250Hz gain"
msgstr ""
#: translations.py:343
msgid "1750Hz gain"
msgstr ""
#: translations.py:344
msgid "2500Hz gain"
msgstr ""
#: translations.py:345
msgid "3500Hz gain"
msgstr ""
#: translations.py:346
msgid "5000Hz gain"
msgstr ""
#: translations.py:347
msgid "100000Hz gain"
msgstr ""
#: translations.py:348
msgid "200000Hz gain"
msgstr ""
#: translations.py:350
msgid "Reverb time"
msgstr ""
#: translations.py:352 translations.py:355
msgid "Dry/Wet mix"
msgstr ""
#: translations.py:354
msgid "Effect cutoff(Hz)"
msgstr ""
#: translations.py:357
msgid "Rate"
msgstr ""
#: translations.py:359
msgid "Sift"
msgstr ""
#: translations.py:363
msgid "Year"
msgstr ""
#: translations.py:364
msgid "RPM"
msgstr ""
#: translations.py:365
msgid "Surface warping"
msgstr ""
#: translations.py:366
msgid "Cracle"
msgstr ""
#: translations.py:367
msgid "Wear"
msgstr ""
#: translations.py:369
msgid "Number of voices"
msgstr ""
#: translations.py:370
msgid "Delay base(ms)"
msgstr ""
#: translations.py:371
msgid "Voice separation(ms)"
msgstr ""
#: translations.py:372
msgid "Detune(%)"
msgstr ""
#: translations.py:373
msgid "Oscillation frequency(Hz)"
msgstr ""
#: translations.py:374
msgid "Output attenuation(dB)"
msgstr ""
#: translations.py:376
msgid "X Scatter"
msgstr ""
#: translations.py:377
msgid "Y Scatter"
msgstr ""
#: translations.py:378
msgid "Scale"
msgstr ""
#: translations.py:379
msgid "Mix"
msgstr ""
#: translations.py:382
msgid "Delta"
msgstr ""
#: translations.py:383
msgid "Duration"
msgstr ""
#: translations.py:384
msgid "Bright. up"
msgstr ""
#: translations.py:385
msgid "Bright. down"
msgstr ""
#: translations.py:386
msgid "Bright. dur."
msgstr ""
#: translations.py:387
msgid "Develop up"
msgstr ""
#: translations.py:388
msgid "Develop down"
msgstr ""
#: translations.py:389
msgid "Develop dur."
msgstr ""
#: translations.py:390
msgid "Triplevel"
msgstr ""
#: translations.py:391
msgid "Difference Space"
msgstr ""
#: translations.py:392
msgid "Block width"
msgstr ""
#: translations.py:393
msgid "Block height"
msgstr ""
#: translations.py:394 translations.py:440
msgid "Size"
msgstr ""
#: translations.py:403
msgid "U"
msgstr ""
#: translations.py:404
msgid "V"
msgstr ""
#: translations.py:405 translations.py:412
msgid "Black"
msgstr ""
#: translations.py:406 translations.py:413
msgid "White"
msgstr ""
#: translations.py:408
msgid "Neutral Color"
msgstr ""
#: translations.py:409 translations.py:410
msgid "Input"
msgstr ""
#: translations.py:411
msgid "Gamma"
msgstr ""
#: translations.py:414
msgid "Num"
msgstr ""
#: translations.py:415
msgid "Dist. weight"
msgstr ""
#: translations.py:417
msgid "Variance"
msgstr ""
#: translations.py:419
msgid "Red Saturation"
msgstr ""
#: translations.py:420
msgid "Yellow Saturation"
msgstr ""
#: translations.py:421
msgid "Factor"
msgstr ""
#: translations.py:422
msgid "Source color"
msgstr ""
#: translations.py:424
msgid "Amplitude"
msgstr ""
#: translations.py:425
msgid "Frequency"
msgstr ""
#: translations.py:428
msgid "Center Correct"
msgstr ""
#: translations.py:429
msgid "Edges Correct"
msgstr ""
#: translations.py:431
msgid "Axis"
msgstr ""
#: translations.py:434
msgid "Edge Lightning"
msgstr ""
#: translations.py:435
msgid "Edge Brightness"
msgstr ""
#: translations.py:436
msgid "Non-Edge Brightness"
msgstr ""
#: translations.py:437
msgid "Spatial"
msgstr ""
#: translations.py:438
msgid "Temporal"
msgstr ""
#: translations.py:441
msgid "Border width"
msgstr ""
#: translations.py:442
msgid "Phase Incr."
msgstr ""
#: translations.py:443
msgid "Zoom"
msgstr ""
#: translations.py:444
msgid "Freeze Frame"
msgstr ""
#: translations.py:445
msgid "Freeze After"
msgstr ""
#: translations.py:446
msgid "Freeze Before"
msgstr ""
#: translations.py:447
msgid "Angle"
msgstr ""
#: translations.py:448 translations.py:451 translations.py:452
msgid "transition.geometry"
msgstr ""
#: translations.py:449 translations.py:526
msgid "Shear X"
msgstr ""
#: translations.py:450 translations.py:527
msgid "Shear Y"
msgstr ""
#: translations.py:459 translations.py:460 translations.py:525
#: translations.py:529
msgid "Opacity"
msgstr ""
#: translations.py:461
msgid "Rotate X"
msgstr ""
#: translations.py:462
msgid "Rotate Y"
msgstr ""
#: translations.py:463
msgid "Rotate Z"
msgstr ""
#: translations.py:465
msgid "Edge Mode"
msgstr ""
#: translations.py:466
msgid "Sel. Space"
msgstr ""
#: translations.py:467
msgid "Operation"
msgstr ""
#: translations.py:468 translations.py:554
msgid "Hard"
msgstr ""
#: translations.py:469
msgid "Selection subspace"
msgstr ""
#: translations.py:470
msgid "R/A/Hue"
msgstr ""
#: translations.py:471
msgid "G/B/Chroma"
msgstr ""
#: translations.py:472
msgid "B/I/I"
msgstr ""
#: translations.py:473
msgid "Supress"
msgstr ""
#: translations.py:474 translations.py:584
msgid "Horizontal"
msgstr ""
#: translations.py:475 translations.py:585
msgid "Vertical"
msgstr ""
#: translations.py:476
msgid "Type"
msgstr ""
#: translations.py:478
msgid "Dot Radius"
msgstr ""
#: translations.py:479
msgid "Cyan Angle"
msgstr ""
#: translations.py:480
msgid "Magenta Angle"
msgstr ""
#: translations.py:481
msgid "Yellow Angle"
msgstr ""
#: translations.py:483
msgid "Matrix Type"
msgstr ""
#: translations.py:484
msgid "Aspect"
msgstr ""
#: translations.py:485
msgid "Center Size"
msgstr ""
#: translations.py:486
msgid "Azimuth"
msgstr ""
#: translations.py:487 translations.py:492
msgid "Lightness"
msgstr ""
#: translations.py:488
msgid "Bump Height"
msgstr ""
#: translations.py:489
msgid "Gray"
msgstr ""
#: translations.py:490
msgid "Split Preview"
msgstr ""
#: translations.py:491
msgid "Source on Left"
msgstr ""
#: translations.py:493
msgid "Channel"
msgstr ""
#: translations.py:494
msgid "Input black level"
msgstr ""
#: translations.py:495
msgid "Input white level"
msgstr ""
#: translations.py:496
msgid "Black output"
msgstr ""
#: translations.py:497
msgid "White output"
msgstr ""
#: translations.py:501
msgid "Action"
msgstr ""
#: translations.py:502
msgid "Keep Luma"
msgstr ""
#: translations.py:503
msgid "Luma Formula"
msgstr ""
#: translations.py:504
msgid "Effect"
msgstr ""
#: translations.py:506
msgid "Blend Type"
msgstr ""
#: translations.py:508
msgid "Key Color"
msgstr ""
#: translations.py:509
msgid "Pre-Level"
msgstr ""
#: translations.py:510
msgid "Post-Level"
msgstr ""
#: translations.py:511
msgid "Slope"
msgstr ""
#: translations.py:512
msgid "Luma Band"
msgstr ""
#: translations.py:514
msgid "Gain"
msgstr ""
#: translations.py:515
msgid "Input White Level"
msgstr ""
#: translations.py:516
msgid "Input Black Level"
msgstr ""
#: translations.py:517
msgid "Black Output"
msgstr ""
#: translations.py:518
msgid "White Output"
msgstr ""
#: translations.py:519
msgid "Rows"
msgstr ""
#: translations.py:520
msgid "Columns"
msgstr ""
#: translations.py:521
msgid "Color Temperature"
msgstr ""
#: translations.py:522
msgid "Select .cube file"
msgstr ""
#: translations.py:530 translations.py:534
msgid "Wipe Type"
msgstr ""
#: translations.py:532 translations.py:536
msgid "Softness"
msgstr ""
#: translations.py:533
msgid "Wipe Amount"
msgstr ""
#: translations.py:540 translations.py:546
msgid "Shave"
msgstr ""
#: translations.py:541
msgid "Rectangle"
msgstr ""
#: translations.py:542
msgid "Ellipse"
msgstr ""
#: translations.py:543
msgid "Triangle"
msgstr ""
#: translations.py:545 translations.py:559
msgid "Diamond"
msgstr ""
#: translations.py:547
msgid "Shrink Hard"
msgstr ""
#: translations.py:548
msgid "Shrink Soft"
msgstr ""
#: translations.py:549
msgid "Grow Hard"
msgstr ""
#: translations.py:550
msgid "Grow Soft"
msgstr ""
#: translations.py:551
msgid "RGB"
msgstr ""
#: translations.py:552
msgid "ABI"
msgstr ""
#: translations.py:553
msgid "HCI"
msgstr ""
#: translations.py:555
msgid "Fat"
msgstr ""
#: translations.py:556
msgid "Normal"
msgstr ""
#: translations.py:557
msgid "Skinny"
msgstr ""
#: translations.py:558
msgid "Ellipsoid"
msgstr ""
#: translations.py:564 mlttransitions.py:151
msgid "Subtract"
msgstr ""
#: translations.py:567
msgid "Sharper"
msgstr ""
#: translations.py:568
msgid "Fuzzier"
msgstr ""
#: translations.py:569
msgid "Luma"
msgstr ""
#: translations.py:573
msgid "Add Constant"
msgstr ""
#: translations.py:574
msgid "Change Gamma"
msgstr ""
#: translations.py:575 mlttransitions.py:146
msgid "Multiply"
msgstr ""
#: translations.py:576
msgid "XPro"
msgstr ""
#: translations.py:577
msgid "OldPhoto"
msgstr ""
#: translations.py:579
msgid "Heat"
msgstr ""
#: translations.py:580
msgid "XRay"
msgstr ""
#: translations.py:581
msgid "RedGreen"
msgstr ""
#: translations.py:582
msgid "YellowBlue"
msgstr ""
#: translations.py:583
msgid "Esses"
msgstr ""
#: translations.py:586
msgid "Shadows"
msgstr ""
#: translations.py:587
msgid "Midtones"
msgstr ""
#: translations.py:588
msgid "Highlights"
msgstr ""
#: mlttransitions.py:77
msgid "Vertical From Center"
msgstr ""
#: mlttransitions.py:78
msgid "Vertical Top to Bottom"
msgstr ""
#: mlttransitions.py:79
msgid "Vertical Bottom to Top"
msgstr ""
#: mlttransitions.py:80
msgid "Horizontal From Center"
msgstr ""
#: mlttransitions.py:81
msgid "Horizontal Left to Right"
msgstr ""
#: mlttransitions.py:82
msgid "Horizontal Right to Left"
msgstr ""
#: mlttransitions.py:83
msgid "Clock Left To Right"
msgstr ""
#: mlttransitions.py:84
msgid "Clock Right to Left"
msgstr ""
#: mlttransitions.py:85
msgid "Clock Symmetric"
msgstr ""
#: mlttransitions.py:86
msgid "Stripes Horizontal"
msgstr ""
#: mlttransitions.py:87
msgid "Stripes Horizontal Big"
msgstr ""
#: mlttransitions.py:88
msgid "Stripes Horizontal Moving"
msgstr ""
#: mlttransitions.py:89
msgid "Stripes Vertical"
msgstr ""
#: mlttransitions.py:90
msgid "Stripes Vertical Big"
msgstr ""
#: mlttransitions.py:91
msgid "Burst"
msgstr ""
#: mlttransitions.py:92
msgid "Circle From In"
msgstr ""
#: mlttransitions.py:93
msgid "Circle From Out"
msgstr ""
#: mlttransitions.py:94
msgid "Cloud"
msgstr ""
#: mlttransitions.py:95
msgid "Hatched 1"
msgstr ""
#: mlttransitions.py:96
msgid "Hatched 2"
msgstr ""
#: mlttransitions.py:97
msgid "Hourglass"
msgstr ""
#: mlttransitions.py:98
msgid "Puddles"
msgstr ""
#: mlttransitions.py:99
msgid "Rings"
msgstr ""
#: mlttransitions.py:100
msgid "Rectangle From In"
msgstr ""
#: mlttransitions.py:101
msgid "Rectangle From Out"
msgstr ""
#: mlttransitions.py:102
msgid "Rectangle Bars"
msgstr ""
#: mlttransitions.py:103
msgid "Sand"
msgstr ""
#: mlttransitions.py:104
msgid "Sphere"
msgstr ""
#: mlttransitions.py:105
msgid "Spiral Abstract"
msgstr ""
#: mlttransitions.py:106
msgid "Spiral"
msgstr ""
#: mlttransitions.py:107
msgid "Spiral Galaxy"
msgstr ""
#: mlttransitions.py:108
msgid "Spiral Big"
msgstr ""
#: mlttransitions.py:109
msgid "Spiral Medium"
msgstr ""
#: mlttransitions.py:110
msgid "Spots"
msgstr ""
#: mlttransitions.py:111 medialog.py:535
msgid "Star"
msgstr ""
#: mlttransitions.py:112
msgid "Arch"
msgstr ""
#: mlttransitions.py:113
msgid "Patches"
msgstr ""
#: mlttransitions.py:114
msgid "Free Stripes"
msgstr ""
#: mlttransitions.py:115
msgid "Free Curves"
msgstr ""
#: mlttransitions.py:116
msgid "Diagonal 1"
msgstr ""
#: mlttransitions.py:117
msgid "Diagonal 2"
msgstr ""
#: mlttransitions.py:118
msgid "Diagonal 3"
msgstr ""
#: mlttransitions.py:119
msgid "Diagonal 4"
msgstr ""
#: mlttransitions.py:120
msgid "Checkerboard"
msgstr ""
#: mlttransitions.py:124 mlttransitions.py:163
msgid "Dissolve"
msgstr ""
#: mlttransitions.py:125
msgid "Picture in Picture"
msgstr ""
#: mlttransitions.py:126
msgid "Region"
msgstr ""
#: mlttransitions.py:127
msgid "Affine Blend"
msgstr ""
#: mlttransitions.py:128
msgid "Blend"
msgstr ""
#: mlttransitions.py:129
msgid "Wipe Clip Length"
msgstr ""
#: mlttransitions.py:135
msgid "Burn"
msgstr ""
#: mlttransitions.py:136
msgid "Color only"
msgstr ""
#: mlttransitions.py:137
msgid "Darken"
msgstr ""
#: mlttransitions.py:138
msgid "Difference"
msgstr ""
#: mlttransitions.py:139
msgid "Divide"
msgstr ""
#: mlttransitions.py:140
msgid "Dodge"
msgstr ""
#: mlttransitions.py:141
msgid "Grain extract"
msgstr ""
#: mlttransitions.py:142
msgid "Grain merge"
msgstr ""
#: mlttransitions.py:143
msgid "Hardlight"
msgstr ""
#: mlttransitions.py:145
msgid "Lighten"
msgstr ""
#: mlttransitions.py:147
msgid "Overlay"
msgstr ""
#: mlttransitions.py:149
msgid "Screen"
msgstr ""
#: mlttransitions.py:150
msgid "Softlight"
msgstr ""
#: mlttransitions.py:152
msgid "Value"
msgstr ""
#: mlttransitions.py:164
msgid "Wipe"
msgstr ""
#: mlttransitions.py:165
msgid "Color Dip"
msgstr ""
#: propertyeditorbuilder.py:249
msgid "Size/Height"
msgstr ""
#: propertyeditorbuilder.py:285
msgid "Width"
msgstr ""
#: propertyeditorbuilder.py:398
msgid "Preset Luma"
msgstr ""
#: propertyeditorbuilder.py:399
msgid "User Luma"
msgstr ""
#: propertyeditorbuilder.py:401
msgid "Select Luma File"
msgstr ""
#: propertyeditorbuilder.py:410
msgid "Wipe Luma files"
msgstr ""
#: propertyeditorbuilder.py:416
msgid "Luma File:"
msgstr ""
#: propertyeditorbuilder.py:471
msgid "Select File"
msgstr ""
#: propertyeditorbuilder.py:508 propertyeditorbuilder.py:603
msgid "Nothing"
msgstr ""
#: propertyeditorbuilder.py:508 propertyeditorbuilder.py:603
msgid "Deinterlace"
msgstr ""
#: propertyeditorbuilder.py:508 propertyeditorbuilder.py:603
msgid "Both"
msgstr ""
#: propertyeditorbuilder.py:517 propertyeditorbuilder.py:612
msgid "Force"
msgstr ""
#: propertyeditorbuilder.py:522 propertyeditorbuilder.py:617
msgid "Align"
msgstr ""
#: keyframeeditor.py:1550
msgid "View:"
msgstr ""
#: keyframeeditor.py:1556
msgid "Large"
msgstr ""
#: keyframeeditor.py:1557
msgid "Medium"
msgstr ""
#: keyframeeditor.py:1558
msgid "Small"
msgstr ""
#: keyframeeditor.py:1578
msgid "Reset Geometry"
msgstr ""
#: keyframeeditor.py:1579
msgid "Geometry to Original Aspect Ratio"
msgstr ""
#: keyframeeditor.py:1580
msgid "Center Horizontal"
msgstr ""
#: keyframeeditor.py:1581
msgid "Center Vertical"
msgstr ""
#: middlebar.py:127
msgid ""
"Zoom In - Mouse Middle Scroll\n"
" Zoom Out - Mouse Middle Scroll\n"
" Zoom Length - Mouse Middle Click"
msgstr ""
#: middlebar.py:132
msgid ""
"Add Rendered Transition - 2 clips selected\n"
"Add Rendered Fade - 1 clip selected\n"
"Cut - X"
msgstr ""
#: middlebar.py:138
msgid ""
"Splice Out - Delete\n"
"Lift\n"
"Delete Range"
msgstr ""
#: middlebar.py:143
msgid ""
"Resync Selected\n"
"Split Audio"
msgstr ""
#: middlebar.py:150
msgid ""
"Overwrite Range\n"
"Overwrite Clip - T\n"
"Insert Clip - Y\n"
"Append Clip - U"
msgstr ""
#: middlebar.py:155
msgid ""
"Undo - Ctrl + Z\n"
"Redo - Ctrl + Y"
msgstr ""
#: middlebar.py:162
msgid ""
"Audio Mixer\n"
"Titler\n"
"G'Mic Effects\n"
"Batch Render Queue"
msgstr ""
#: middlebar.py:167
msgid ""
"Audio Mixer(not available)\n"
"Titler"
msgstr ""
#: medialog.py:304
msgid "New Group..."
msgstr ""
#: medialog.py:305
msgid "New Group From Selected..."
msgstr ""
#: medialog.py:309
msgid "Rename Current Group..."
msgstr ""
#: medialog.py:315
msgid "Move Selected Items To Group"
msgstr ""
#: medialog.py:318
msgid "No Groups"
msgstr ""
#: medialog.py:331
msgid "Delete Current Group"
msgstr ""
#: medialog.py:337
msgid "Sort by"
msgstr ""
#: medialog.py:340
msgid "Time"
msgstr ""
#: medialog.py:346 medialog.py:541
msgid "File Name"
msgstr ""
#: medialog.py:351 medialog.py:539
msgid "Comment"
msgstr ""
#: medialog.py:465
msgid "Group "
msgstr ""
#: medialog.py:537 projectinfogui.py:100
msgid "Event"
msgstr ""
#: medialog.py:543
msgid "Mark In"
msgstr ""
#: medialog.py:545
msgid "Mark Out"
msgstr ""
#: medialog.py:547 projectinfogui.py:98
msgid "Date"
msgstr ""
#: medialog.py:702
msgid "Use Comments as Clip Names"
msgstr ""
#: medialog.py:733
msgid "Display starred ranges"
msgstr ""
#: medialog.py:734
msgid "Display non-starred ranges"
msgstr ""
#: medialog.py:735
msgid "Set selected ranges starred"
msgstr ""
#: medialog.py:736
msgid "Set selected ranges non-starred"
msgstr ""
#: medialog.py:737
msgid "Log current marked range"
msgstr ""
#: medialog.py:738
msgid "Delete selected ranges"
msgstr ""
#: medialog.py:739
msgid "Insert selected ranges on Timeline"
msgstr ""
#: medialog.py:740
msgid "Append displayed ranges on Timeline"
msgstr ""
#: medialog.py:757
msgid "All Items"
msgstr ""
#: medialog.py:764
msgid "Select viewed Range Log Items Group"
msgstr ""
#: projectinfogui.py:38
msgid "Name"
msgstr ""
#: projectinfogui.py:50
msgid "Project Events"
msgstr ""
#: projectinfogui.py:102
msgid "Path"
msgstr ""
#: tools/titler.py:85
msgid "Titler is already open"
msgstr ""
#: tools/titler.py:86
msgid "Only single instance of Titler can be opened."
msgstr ""
#: tools/titler.py:318
msgid "Load Layers"
msgstr ""
#: tools/titler.py:320
msgid "Save Layers"
msgstr ""
#: tools/titler.py:322
msgid "Clear All"
msgstr ""
#: tools/titler.py:399
msgid "Active Layer"
msgstr ""
#: tools/titler.py:400
msgid "Layers"
msgstr ""
#: tools/titler.py:406
msgid "Keep Layers When Closed"
msgstr ""
#: tools/titler.py:411
msgid "Open Saved Title In Bin"
msgstr ""
#: tools/titler.py:418
msgid "Save Title Graphic"
msgstr ""
#: rendergui.py:45
msgid "Render Progress"
msgstr ""
#: rendergui.py:69
msgid ""
"Project and Render Profile FPS values are not same. Rendered file may have A/"
"V sync issues."
msgstr ""
#: rendergui.py:96
msgid "Render range not defined!"
msgstr ""
#: rendergui.py:97
msgid ""
"Define render range using Mark In and Mark Out points\n"
"or select range option 'Sequence length' to start rendering."
msgstr ""
#: rendergui.py:101
msgid "Load Render Args File"
msgstr ""
#: rendergui.py:115
msgid "Save Render Args As"
msgstr ""
#: rendergui.py:169
msgid "Render Slow/Fast Motion Video File"
msgstr ""
#: rendergui.py:174
msgid "Source Media File: "
msgstr ""
#: rendergui.py:181 rendergui.py:182
msgid "not set"
msgstr ""
#: rendergui.py:202
msgid "Select Target Folder"
msgstr ""
#: rendergui.py:206
msgid "Speed %:"
msgstr ""
#: rendergui.py:244
msgid "Full Source Length"
msgstr ""
#: rendergui.py:249
msgid "Source Mark In to Mark Out"
msgstr ""
#: rendergui.py:272
msgid "Source Mark In: "
msgstr ""
#: rendergui.py:273
msgid "Source Mark Out: "
msgstr ""
#: rendergui.py:277
msgid "Target File:"
msgstr ""
#: rendergui.py:278
msgid "Target Folder:"
msgstr ""
#: rendergui.py:279
msgid "Target Profile:"
msgstr ""
#: rendergui.py:280
msgid "Target Encoding:"
msgstr ""
#: rendergui.py:281
msgid "Target Quality:"
msgstr ""
#: rendergui.py:283 rendergui.py:468
msgid "Render Range:"
msgstr ""
#: rendergui.py:284
msgid "Rendered Clip Length:"
msgstr ""
#: rendergui.py:317 tools/toolsencoding.py:172
msgid "Select Render quality"
msgstr ""
#: rendergui.py:335
msgid "Select audio sample frequency"
msgstr ""
#: rendergui.py:359 tools/toolsencoding.py:231
msgid "Select Render encoding"
msgstr ""
#: rendergui.py:396 tools/toolsencoding.py:155
msgid "Select render profile"
msgstr ""
#: rendergui.py:423
msgid "Full Length"
msgstr ""
#: rendergui.py:424
msgid "Marked Range"
msgstr ""
#: rendergui.py:432 tools/toolsencoding.py:270
msgid "File"
msgstr ""
#: rendergui.py:433 tools/toolsencoding.py:271
msgid "Render Profile"
msgstr ""
#: rendergui.py:436 rendergui.py:454 tools/toolsencoding.py:272
msgid "Encoding Format"
msgstr ""
#: rendergui.py:438 tools/toolsencoding.py:273
msgid "Render Type"
msgstr ""
#: rendergui.py:456 rendergui.py:924
msgid "Render Args"
msgstr ""
#: rendergui.py:460
msgid "Open File in Bin:"
msgstr ""
#: rendergui.py:537 tools/toolsencoding.py:60
msgid "Select folder to place rendered file in"
msgstr ""
#: rendergui.py:538 tools/toolsencoding.py:61
msgid "Give name for rendered file"
msgstr ""
#: rendergui.py:545 tools/toolsencoding.py:68
msgid "Presets:"
msgstr ""
#: rendergui.py:548 tools/toolsencoding.py:71
msgid "User Defined"
msgstr ""
#: rendergui.py:549 tools/toolsencoding.py:72
msgid "Preset File type"
msgstr ""
#: rendergui.py:566 rendergui.py:642 rendergui.py:758
msgid "Use Project Profile:"
msgstr ""
#: rendergui.py:567 rendergui.py:643 rendergui.py:759
msgid "Render using args:"
msgstr ""
#: rendergui.py:582
msgid "Select used project profile for rendering"
msgstr ""
#: rendergui.py:583
msgid "Render profile info"
msgstr ""
#: rendergui.py:660 rendergui.py:885
msgid "Load Selection"
msgstr ""
#: rendergui.py:665 rendergui.py:888
msgid "Ext.:"
msgstr ""
#: rendergui.py:700 rendergui.py:797
msgid "Render using key=value rendering options"
msgstr ""
#: rendergui.py:701 rendergui.py:895
msgid "Load render options from currently selected encoding"
msgstr ""
#: rendergui.py:702 rendergui.py:896
msgid "Edit render options"
msgstr ""
#: rendergui.py:703 rendergui.py:799
msgid "Save Render Args into a text file"
msgstr ""
#: rendergui.py:704 rendergui.py:800
msgid "Load Render Args from a text file"
msgstr ""
#: rendergui.py:777
msgid "Edit Args:"
msgstr ""
#: rendergui.py:907
msgid "Set Args"
msgstr ""
#: profilesmanager.py:47 proxyediting.py:218
msgid "Close Manager"
msgstr ""
#: profilesmanager.py:72 tools/batchrendering.py:649
msgid "Delete Selected"
msgstr ""
#: profilesmanager.py:82
msgid "Load Profile Values"
msgstr ""
#: profilesmanager.py:117
msgid "Save New Profile"
msgstr ""
#: profilesmanager.py:129
msgid "Description.:"
msgstr ""
#: profilesmanager.py:130
msgid "Frame rate num.:"
msgstr ""
#: profilesmanager.py:131
msgid "Frame rate den.:"
msgstr ""
#: profilesmanager.py:132
msgid "Width:"
msgstr ""
#: profilesmanager.py:133
msgid "Height:"
msgstr ""
#: profilesmanager.py:134
msgid "Sample aspect num.:"
msgstr ""
#: profilesmanager.py:135
msgid "Sample aspect den.:"
msgstr ""
#: profilesmanager.py:136
msgid "Display aspect num.:"
msgstr ""
#: profilesmanager.py:137
msgid "Display aspect den.:"
msgstr ""
#: profilesmanager.py:166
msgid "Create User Profile"
msgstr ""
#: profilesmanager.py:167
msgid "User Profiles"
msgstr ""
#: profilesmanager.py:175
msgid "Visible"
msgstr ""
#: profilesmanager.py:177
msgid "Hide Selected"
msgstr ""
#: profilesmanager.py:179
msgid "Hidden"
msgstr ""
#: profilesmanager.py:181
msgid "Unhide Selected"
msgstr ""
#: profilesmanager.py:210
msgid "Factory Profiles"
msgstr ""
#: profilesmanager.py:216
msgid "User "
msgstr ""
#: profilesmanager.py:258 profilesmanager.py:266
msgid "Profile '"
msgstr ""
#: profilesmanager.py:258
msgid "' already exists!"
msgstr ""
#: profilesmanager.py:259
msgid "Delete profile and save again."
msgstr ""
#: profilesmanager.py:266
msgid "' saved."
msgstr ""
#: profilesmanager.py:267
msgid "You can now create a new project using the new profile."
msgstr ""
#: profilesmanager.py:279
msgid "Confirm user profile delete"
msgstr ""
#: profilesmanager.py:280 tools/batchrendering.py:732
msgid "This operation cannot be undone."
msgstr ""
#: preferenceswindow.py:43
msgid "Editor Preferences"
msgstr ""
#: preferenceswindow.py:57
msgid "Editing"
msgstr ""
#: preferenceswindow.py:59
msgid "Performance"
msgstr ""
#: preferenceswindow.py:81
msgid "Restart required for some setting changes to take effect."
msgstr ""
#: preferenceswindow.py:82
msgid "If requested change is not in effect, restart application."
msgstr ""
#: preferenceswindow.py:116
msgid "No Autosave"
msgstr ""
#: preferenceswindow.py:116
msgid "1 min"
msgstr ""
#: preferenceswindow.py:116
msgid "2 min"
msgstr ""
#: preferenceswindow.py:116
msgid "5 min"
msgstr ""
#: preferenceswindow.py:124
msgid "Absolute paths first, relative second"
msgstr ""
#: preferenceswindow.py:125
msgid "Relative paths first, absolute second"
msgstr ""
#: preferenceswindow.py:126
msgid "Absolute paths only"
msgstr ""
#: preferenceswindow.py:130
msgid "Default Profile:"
msgstr ""
#: preferenceswindow.py:131
msgid "Remember last media directory"
msgstr ""
#: preferenceswindow.py:132
msgid "Undo stack size:"
msgstr ""
#: preferenceswindow.py:133
msgid "Thumbnail folder:"
msgstr ""
#: preferenceswindow.py:134
msgid "Remember last render directory"
msgstr ""
#: preferenceswindow.py:135
msgid "Autosave for crash recovery every:"
msgstr ""
#: preferenceswindow.py:136
msgid "Rendered Clips folder:"
msgstr ""
#: preferenceswindow.py:137
msgid "Media look-up order on load:"
msgstr ""
#: preferenceswindow.py:182
msgid "Overwrite blanks"
msgstr ""
#: preferenceswindow.py:183
msgid "Always insert"
msgstr ""
#: preferenceswindow.py:199
msgid "Zoom, Control to Scroll Horizontal"
msgstr ""
#: preferenceswindow.py:200
msgid "Scroll Horizontal, Control to Zoom"
msgstr ""
#: preferenceswindow.py:211
msgid "Autoplay new Clips in Clip Monitor"
msgstr ""
#: preferenceswindow.py:212
msgid "Center Current Frame on Playback Stop"
msgstr ""
#: preferenceswindow.py:213
msgid "Center Current Frame after Up/Down Arrow"
msgstr ""
#: preferenceswindow.py:214
msgid "Graphics default length:"
msgstr ""
#: preferenceswindow.py:215
msgid "Trim Modes exit on empty click"
msgstr ""
#: preferenceswindow.py:216
msgid "Quick enter Trim Modes"
msgstr ""
#: preferenceswindow.py:217
msgid "Remember Monitor Clip Frame"
msgstr ""
#: preferenceswindow.py:218
msgid "Media drag'n'drop action on non-V1 tracks"
msgstr ""
#: preferenceswindow.py:219
msgid "Cover Transition/Fade clips on delete if possible"
msgstr ""
#: preferenceswindow.py:221
msgid "Enable single Play/Pause button"
msgstr ""
#: preferenceswindow.py:222
msgid "Mouse Middle Button Scroll Action"
msgstr ""
#: preferenceswindow.py:223
msgid "Hide file extensions when importing Clips"
msgstr ""
#: preferenceswindow.py:263
msgid "Glass"
msgstr ""
#: preferenceswindow.py:264
msgid "Simple"
msgstr ""
#: preferenceswindow.py:271
msgid "Light Theme"
msgstr ""
#: preferenceswindow.py:272
msgid "Dark Theme"
msgstr ""
#: preferenceswindow.py:284
msgid "Display All Levels"
msgstr ""
#: preferenceswindow.py:285
msgid "Display Levels On Request"
msgstr ""
#: preferenceswindow.py:292
msgid "Single Window"
msgstr ""
#: preferenceswindow.py:293
msgid "Two Windows"
msgstr ""
#: preferenceswindow.py:300
msgid "Application window mode:"
msgstr ""
#: preferenceswindow.py:301
msgid "Use English texts on localized OS"
msgstr ""
#: preferenceswindow.py:302
msgid "Display splash screen"
msgstr ""
#: preferenceswindow.py:303
msgid "Buttons style:"
msgstr ""
#: preferenceswindow.py:304
msgid "Theme request, icons and colors:"
msgstr ""
#: preferenceswindow.py:305
msgid "Theme detection fail fallback colors:"
msgstr ""
#: preferenceswindow.py:306
msgid "Default audio levels display:"
msgstr ""
#: preferenceswindow.py:308
msgid "Show Full File names"
msgstr ""
#: preferenceswindow.py:347
msgid "Between 1 and the number of CPU Cores"
msgstr ""
#: preferenceswindow.py:348
msgid "Allow Frame Dropping for real-time rendering, when needed"
msgstr ""
#: preferenceswindow.py:351
msgid "Render Threads:"
msgstr ""
#: preferenceswindow.py:352
msgid "Allow Frame Dropping"
msgstr ""
#: tools/batchrendering.py:298
msgid "Render Item Project File Copy failed!"
msgstr ""
#: tools/batchrendering.py:365 tools/batchrendering.py:749
msgid "Error loading render queue items!"
msgstr ""
#: tools/batchrendering.py:366 tools/batchrendering.py:750
msgid "Message:\n"
msgstr ""
#: tools/batchrendering.py:384
msgid "Batch Render Queue already running!"
msgstr ""
#: tools/batchrendering.py:386
msgid "Batch Render Queue application was detected in session dbus."
msgstr ""
#: tools/batchrendering.py:408
msgid "Application is rendering and cannot be closed!"
msgstr ""
#: tools/batchrendering.py:409
msgid "Stop rendering before closing the application."
msgstr ""
#: tools/batchrendering.py:441
msgid " datafile load failed with "
msgstr ""
#: tools/batchrendering.py:447
msgid " project file load failed with "
msgstr ""
#: tools/batchrendering.py:561
msgid "Queued"
msgstr ""
#: tools/batchrendering.py:563
msgid "Rendering"
msgstr ""
#: tools/batchrendering.py:565
msgid "Finished"
msgstr ""
#: tools/batchrendering.py:567
msgid "Unqueued"
msgstr ""
#: tools/batchrendering.py:569
msgid "Aborted"
msgstr ""
#: tools/batchrendering.py:624 tools/batchrendering.py:1241
msgid "Estimated Left:"
msgstr ""
#: tools/batchrendering.py:625
msgid "Current Render:"
msgstr ""
#: tools/batchrendering.py:626 tools/batchrendering.py:1243 proxyediting.py:364
msgid "Elapsed:"
msgstr ""
#: tools/batchrendering.py:637
msgid "Items Rendered:"
msgstr ""
#: tools/batchrendering.py:639
msgid "Render Started:"
msgstr ""
#: tools/batchrendering.py:645
msgid "Not Rendering"
msgstr ""
#: tools/batchrendering.py:653
msgid "Delete Finished"
msgstr ""
#: tools/batchrendering.py:658
msgid "Reload Queue"
msgstr ""
#: tools/batchrendering.py:669 tools/batchrendering.py:1253
msgid "Stop Render"
msgstr ""
#: tools/batchrendering.py:705
msgid "Flowblade Batch Render"
msgstr ""
#: tools/batchrendering.py:731
msgid "Delete "
msgstr ""
#: tools/batchrendering.py:731
msgid " item(s) from render queue?"
msgstr ""
#: tools/batchrendering.py:762
msgid "Multiple items with same render target file!"
msgstr ""
#: tools/batchrendering.py:764
msgid ""
"Later items will render on top of earlier items if this queue is rendered.\n"
msgstr ""
#: tools/batchrendering.py:765
msgid ""
"Delete or unqueue some items with same paths:\n"
"\n"
msgstr ""
#: tools/batchrendering.py:767
msgid " items with path: "
msgstr ""
#: tools/batchrendering.py:866
msgid "Project/Sequence"
msgstr ""
#: tools/batchrendering.py:867
msgid "Status"
msgstr ""
#: tools/batchrendering.py:868
msgid "Render File"
msgstr ""
#: tools/batchrendering.py:869
msgid "Render Time"
msgstr ""
#: tools/batchrendering.py:960
msgid "Save Render Item Project As"
msgstr ""
#: tools/batchrendering.py:996
msgid "Encoding:"
msgstr ""
#: tools/batchrendering.py:997
msgid "Quality:"
msgstr ""
#: tools/batchrendering.py:998
msgid "Audio Encoding:"
msgstr ""
#: tools/batchrendering.py:999
msgid "Use User Args:"
msgstr ""
#: tools/batchrendering.py:1000
msgid "Start:"
msgstr ""
#: tools/batchrendering.py:1001
msgid "End:"
msgstr ""
#: tools/batchrendering.py:1003
msgid "Render Profile Name:"
msgstr ""
#: tools/batchrendering.py:1004
msgid "Render Profile:"
msgstr ""
#: tools/batchrendering.py:1020 tools/batchrendering.py:1028
msgid "Render Properties"
msgstr ""
#: tools/batchrendering.py:1027
msgid "Save Item Project As..."
msgstr ""
#: tools/batchrendering.py:1278
msgid "Flowblade Timeline Render"
msgstr ""
#: proxyediting.py:239
msgid "Project Image Size"
msgstr ""
#: proxyediting.py:240
msgid "Half Project Image Size"
msgstr ""
#: proxyediting.py:241
msgid "Quarter Project Image Size"
msgstr ""
#: proxyediting.py:257
msgid "Proxy Encoding"
msgstr ""
#: proxyediting.py:269
msgid "Proxy Stats:"
msgstr ""
#: proxyediting.py:270
msgid " proxy file(s) for "
msgstr ""
#: proxyediting.py:270
msgid " video file(s)"
msgstr ""
#: proxyediting.py:273
msgid "Current Proxy Mode:"
msgstr ""
#: proxyediting.py:280 proxyediting.py:344
msgid "Press Button to Change Mode"
msgstr ""
#: proxyediting.py:282
msgid "Use Proxy Media"
msgstr ""
#: proxyediting.py:283
msgid "Use Original Media"
msgstr ""
#: proxyediting.py:304
msgid "Project Proxy Mode"
msgstr ""
#: proxyediting.py:330
msgid "Using Proxy Media"
msgstr ""
#: proxyediting.py:332
msgid "Using Original Media"
msgstr ""
#: proxyediting.py:350
msgid "Creating Proxy Files"
msgstr ""
#: proxyediting.py:365
msgid "Current Media File:"
msgstr ""
#: proxyediting.py:366
msgid "Rendering Item:"
msgstr ""
#: proxyediting.py:406
msgid "Proxy Render Info"
msgstr ""
#: proxyediting.py:421
msgid "Nothing will be rendered"
msgstr ""
#: proxyediting.py:422
msgid ""
"No video files were selected.\n"
"Only video files can have proxy files."
msgstr ""
#: proxyediting.py:430
msgid "Do Render Action"
msgstr ""
#: proxyediting.py:435
msgid "Proxies exist that were created by this and other projects for "
msgstr ""
#: proxyediting.py:435 proxyediting.py:438 proxyediting.py:441
msgid " file(s).\n"
msgstr ""
#: proxyediting.py:438
msgid "Proxies have already been created for "
msgstr ""
#: proxyediting.py:441
msgid "Proxies exist that were created by other projects for "
msgstr ""
#: proxyediting.py:444 proxyediting.py:447
msgid "You are trying to create proxies for "
msgstr ""
#: proxyediting.py:444
msgid " non-video file(s).\n"
msgstr ""
#: proxyediting.py:447
msgid " proxy file(s).\n"
msgstr ""
#: proxyediting.py:454
msgid "Render Unrendered Possible & Use existing"
msgstr ""
#: proxyediting.py:455
msgid "Rerender All Possible"
msgstr ""
#: proxyediting.py:457
msgid "Select Render Action: "
msgstr ""
#: proxyediting.py:678
msgid "Converting Project to Use Proxy Media"
msgstr ""
#: proxyediting.py:688
msgid "Converting to Use Original Media"
msgstr ""
#: tlineaction.py:200
msgid "Fade/Transition cover delete failed!"
msgstr ""
#: tlineaction.py:201
msgid ""
"There wasn't enough material available in adjacent clips.\n"
"A normal Splice Out was done instead."
msgstr ""
#: tlineaction.py:334
msgid "No Clips are selected!"
msgstr ""
#: tlineaction.py:335
msgid "You need to select clips to overwrite to perform this edit."
msgstr ""
#: tlineaction.py:394 tlineaction.py:449
msgid "Timeline Range not set!"
msgstr ""
#: tlineaction.py:395 tlineaction.py:450
msgid ""
"You need to set Timeline Range using Mark In and Mark Out buttons\n"
"to perform this edit."
msgstr ""
#: tlineaction.py:482
msgid "Origin clip not found!"
msgstr ""
#: tlineaction.py:483
msgid ""
"Clip used to create this Compositor has been removed\n"
"or moved to different track."
msgstr ""
#: tlineaction.py:716 tlineaction.py:886
msgid "Rendering "
msgstr ""
#: tlineaction.py:748
msgid ""
"To create a rendered transition you need enough media overlap from both "
"clips!\n"
"\n"
msgstr ""
#: tlineaction.py:753
msgid "FIRST CLIP MEDIA OVERLAP: "
msgstr ""
#: tlineaction.py:754 tlineaction.py:762
msgid "Available "
msgstr ""
#: tlineaction.py:754 tlineaction.py:762
msgid " frame(s), "
msgstr ""
#: tlineaction.py:755 tlineaction.py:763
msgid "Required "
msgstr ""
#: tlineaction.py:755
msgid " frame(s)"
msgstr ""
#: tlineaction.py:761
msgid "SECOND CLIP MEDIA OVERLAP: "
msgstr ""
#: tlineaction.py:763
msgid " frame(s) "
msgstr ""
#: tlineaction.py:770
msgid "Current situation, not enought media overlap:"
msgstr ""
#: tlineaction.py:772
msgid "You need more media overlap:"
msgstr ""
#: tlineaction.py:801
msgid "More media overlap needed to create transition!"
msgstr ""
#: tlineaction.py:820
msgid "Only Video Track mix / fades available"
msgstr ""
#: tlineaction.py:821
msgid ""
"Unfortunately rendered mixes and fades can currently\n"
"only be applied on clips on Video Tracks."
msgstr ""
#: tlineaction.py:855
msgid ""
"Clip is too short for the requested fade:\n"
"\n"
msgstr ""
#: tlineaction.py:856
msgid "Clip Length: "
msgstr ""
#: tlineaction.py:856 tlineaction.py:857
msgid " frame(s)\n"
msgstr ""
#: tlineaction.py:857
msgid "Fade Length: "
msgstr ""
#: tlineaction.py:858
msgid "Clip is too short!"
msgstr ""
#: tlineaction.py:927
msgid "No Clip loaded into Monitor"
msgstr ""
#: tlineaction.py:928
msgid "Can't do the requested edit because there is no Clip in Monitor."
msgstr ""
#: tlineaction.py:932
msgid "Defined range in Monitor Clip is too short"
msgstr ""
#: tlineaction.py:933
msgid ""
"Can't do the requested edit because Mark In -> Mark Out Range or Clip is too "
"short."
msgstr ""
#: trackaction.py:65
msgid "Not enough vertical space on Timeline to expand track"
msgstr ""
#: trackaction.py:66
msgid ""
"Maximize or resize application window to get more\n"
"space for tracks if possible."
msgstr ""
#: medialinker.py:104
msgid "Load Project For Relinking"
msgstr ""
#: medialinker.py:112
msgid "Original Media Missing:"
msgstr ""
#: medialinker.py:113
msgid "Original Media Found:"
msgstr ""
#: medialinker.py:116
msgid "Project:"
msgstr ""
#: medialinker.py:117
msgid ""
msgstr ""
#: medialinker.py:140
msgid "Display Missing Media Files"
msgstr ""
#: medialinker.py:141
msgid "Display Found Media Files"
msgstr ""
#: medialinker.py:152
msgid "Save Relinked Project As..."
msgstr ""
#: medialinker.py:269
msgid "Missing Media File Path"
msgstr ""
#: medialinker.py:270
msgid "Found Media File Path"
msgstr ""
#: medialinker.py:274
msgid "Media File Re-link Path"
msgstr ""
#: medialinker.py:435
msgid "Select Media File To Relink To"
msgstr ""
#: medialinker.py:475
msgid "Original path: "
msgstr ""
#: medialinker.py:478
msgid "Relink path: "
msgstr ""
#: medialinker.py:524
msgid "Relinked version of the Project saved!"
msgstr ""
#: medialinker.py:525
msgid ""
"To test the project, close this tool and open the relinked version in "
"Flowblade."
msgstr ""
#: patternproducer.py:317
msgid "Create Color Clip"
msgstr ""
#: patternproducer.py:323
msgid "Color Clip"
msgstr ""
#: patternproducer.py:331
msgid "Clip Name:"
msgstr ""
#: patternproducer.py:332
msgid "Select Color:"
msgstr ""
#: patternproducer.py:350
msgid "Create Ising Clip"
msgstr ""
#: patternproducer.py:359
msgid "Noise temperature:"
msgstr ""
#: patternproducer.py:360
msgid "Border growth:"
msgstr ""
#: patternproducer.py:361
msgid "Spontanious growth:"
msgstr ""
#: patternproducer.py:381
msgid "Create Color Pulse Clip"
msgstr ""
#: patternproducer.py:393
msgid "Speed 1:"
msgstr ""
#: patternproducer.py:394
msgid "Speed 2:"
msgstr ""
#: patternproducer.py:395
msgid "Speed 3:"
msgstr ""
#: patternproducer.py:396
msgid "Speed 4:"
msgstr ""
#: patternproducer.py:397
msgid "Move 1:"
msgstr ""
#: patternproducer.py:398
msgid "Move 2:"
msgstr ""
#: tools/gmic.py:115
msgid "G'Mic not found!"
msgstr ""
#: tools/gmic.py:116
msgid ""
"G'Mic binary was not present at /usr/bin/gmic.\n"
"Install G'MIC to use this tool."
msgstr ""
#: tools/gmic.py:262
msgid "Select Video Media"
msgstr ""
#: tools/gmic.py:337 tools/gmic.py:738
msgid "not set"
msgstr ""
#: tools/gmic.py:355
msgid "Save Gmic Script As"
msgstr ""
#: tools/gmic.py:379
msgid "Load Gmic Script"
msgstr ""
#: tools/gmic.py:508
msgid "Video Encoding Settings"
msgstr ""
#: tools/gmic.py:512
msgid "Set Encoding"
msgstr ""
#: tools/gmic.py:557 tools/gmic.py:890
msgid "Load Clip"
msgstr ""
#: tools/gmic.py:561
msgid "no clip loaded"
msgstr ""
#: tools/gmic.py:586
msgid "no preview"
msgstr ""
#: tools/gmic.py:633
msgid "Preview"
msgstr ""
#: tools/gmic.py:659
msgid "Add to Script"
msgstr ""
#: tools/gmic.py:719
msgid "Frames Folder:"
msgstr ""
#: tools/gmic.py:730
msgid "Encode Video"
msgstr ""
#: tools/gmic.py:735
msgid "Encoding settings"
msgstr ""
#: tools/gmic.py:754
msgid "Set Mark In, Mark Out and Frames Folder for valid render"
msgstr ""
#: tools/gmic.py:797
msgid "Load Script"
msgstr ""
#: tools/gmic.py:799
msgid "Save Script"
msgstr ""
#: tools/gmic.py:858
msgid "frames"
msgstr ""
#: tools/gmic.py:876
msgid " no video file"
msgstr ""
#: tools/gmic.py:878
msgid " render video file"
msgstr ""
#: tools/gmic.py:879
msgid " frame(s),"
msgstr ""
#: tools/gmic.py:891
msgid "G'Mic Webpage"
msgstr ""
#: tools/gmic.py:1095
msgid "Rendering preview..."
msgstr ""
#: tools/gmic.py:1127
msgid "Preview for frame: "
msgstr ""
#: tools/gmic.py:1128
msgid ", render time: "
msgstr ""
#: tools/gmic.py:1196
msgid "Waiting for frames write to complete..."
msgstr ""
#: tools/gmic.py:1209
msgid "Rendering frame: "
msgstr ""
#: tools/gmic.py:1239
msgid "Render error!"
msgstr ""
#: tools/gmic.py:1281
msgid "Rendering video, "
msgstr ""
#: tools/gmic.py:1281
#, python-format
msgid "% done"
msgstr ""
#: tools/gmic.py:1291
msgid "Render complete!"
msgstr ""
#: tools/gmic.py:1301
msgid "Writing clip frame: "
msgstr ""
#: tools/gmic.py:1313
msgid "Render stopped!"
msgstr ""
#: tools/toolsencoding.py:90
msgid "Use Default Profile:"
msgstr ""
#: monitorevent.py:301
msgid "On some systems Trim View may update slowly"
msgstr ""
#: monitorevent.py:302
msgid ""
"Trim View works best with SSDs and relatively powerful processors.\n"
"\n"
msgstr ""
#: monitorevent.py:303
msgid ""
"Select 'Trim View Off' or'Trim View Single Side Edits Only' "
"options\n"
"if performance is not satisfactory."
msgstr ""
flowblade-1.12/flowblade-trunk/Flowblade/locale/add_language 0000775 0000000 0000000 00000001740 13062777160 0024166 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-1.12/flowblade-trunk/Flowblade/locale/compile_language 0000775 0000000 0000000 00000001134 13062777160 0025063 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-1.12/flowblade-trunk/Flowblade/locale/copy_to_usr 0000775 0000000 0000000 00000000773 13062777160 0024145 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-1.12/flowblade-trunk/Flowblade/locale/create_pot 0000775 0000000 0000000 00000001534 13062777160 0023721 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
flowblade-1.12/flowblade-trunk/Flowblade/locale/cs/ 0000775 0000000 0000000 00000000000 13062777160 0022250 5 ustar 00root root 0000000 0000000 flowblade-1.12/flowblade-trunk/Flowblade/locale/cs/LC_MESSAGES/ 0000775 0000000 0000000 00000000000 13062777160 0024035 5 ustar 00root root 0000000 0000000 flowblade-1.12/flowblade-trunk/Flowblade/locale/cs/LC_MESSAGES/flowblade.mo 0000664 0000000 0000000 00000241533 13062777160 0026341 0 ustar 00root root 0000000 0000000 ' N pi F qi > i i
j J j &