├── .gitignore ├── LICENCE.TXT ├── Makefile ├── README.md ├── bin └── recordwin.sh ├── docs ├── changes.html ├── index-j.html ├── index.html ├── pyvnc2swf-j.html ├── pyvnc2swf.html └── recordwin.html ├── icons ├── player.png └── recorder.png ├── pyvnc2swf ├── Makefile ├── __init__.py ├── d3des.py ├── edit.py ├── html_templates.py ├── image.py ├── movie.py ├── mp3.py ├── output.py ├── play.py ├── record_sound.py ├── rfb.py ├── swf.py └── vnc2swf.py ├── recordings └── .placeholder └── tools ├── .gitignore ├── config ├── install.sh ├── novnc-record.sh ├── play.sh └── record.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.novnc 3 | *.vnc 4 | -------------------------------------------------------------------------------- /LICENCE.TXT: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Library General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | Appendix: How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 19yy 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License 307 | along with this program; if not, write to the Free Software 308 | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 309 | USA. 310 | 311 | Also add information on how to contact you by electronic and paper mail. 312 | 313 | If the program is interactive, make it output a short notice like this 314 | when it starts in an interactive mode: 315 | 316 | Gnomovision version 69, Copyright (C) 19yy name of author 317 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 318 | This is free software, and you are welcome to redistribute it 319 | under certain conditions; type `show c' for details. 320 | 321 | The hypothetical commands `show w' and `show c' should show the appropriate 322 | parts of the General Public License. Of course, the commands you use may 323 | be called something other than `show w' and `show c'; they could even be 324 | mouse-clicks or menu items--whatever suits your program. 325 | 326 | You should also get your employer (if you work as a programmer) or your 327 | school, if any, to sign a "copyright disclaimer" for the program, if 328 | necessary. Here is a sample; alter the names: 329 | 330 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 331 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 332 | 333 | , 1 April 1989 334 | Ty Coon, President of Vice 335 | 336 | This General Public License does not permit incorporating your program into 337 | proprietary programs. If your program is a subroutine library, you may 338 | consider it more useful to permit linking proprietary applications with the 339 | library. If this is what you want to do, use the GNU Library General 340 | Public License instead of this License. 341 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile (only for maintainance purpose) 2 | # $Id: Makefile,v 1.29 2008/11/16 02:54:22 euske Exp $ 3 | # 4 | # Copyright (C) 2005 by Yusuke Shinyama (yusuke at cs . nyu . edu) 5 | # All Rights Reserved. 6 | # 7 | # This is free software; you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation; either version 2 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This software is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this software; if not, write to the Free Software 19 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 20 | # USA. 21 | # 22 | 23 | CVSROOT=:ext:euske@vnc2swf.cvs.sourceforge.net:/cvsroot/vnc2swf 24 | PACKAGE=pyvnc2swf 25 | VERSION=0.9.5 26 | 27 | PYTHON=python 28 | GNUTAR=tar 29 | ZIP=zip 30 | CVS=cvs -d$(CVSROOT) 31 | 32 | WORKDIR=/tmp 33 | DISTNAME=$(PACKAGE)-$(VERSION) 34 | 35 | all: 36 | 37 | clean: 38 | -rm *~ '.#*' .DS_Store 39 | -cd bin && rm *~ '.#*' .DS_Store 40 | -cd docs && rm *~ '.#*' .DS_Store 41 | cd pyvnc2swf && make clean 42 | 43 | up: clean 44 | $(CVS) update 45 | diff: clean 46 | $(CVS) diff -u 47 | commit: clean 48 | $(CVS) commit 49 | 50 | check: 51 | cd pyvnc2swf && pychecker vnc2swf.py 52 | 53 | pack: clean 54 | cd $(WORKDIR) && $(CVS) export -D now -d $(DISTNAME) pyvnc2swf 55 | cd $(WORKDIR) && $(GNUTAR) c -z -f $(WORKDIR)/$(DISTNAME).tar.gz $(DISTNAME) --dereference --numeric-owner 56 | cd $(WORKDIR) && $(ZIP) -r $(DISTNAME).zip $(DISTNAME) 57 | cd $(WORKDIR) && rm -rf $(DISTNAME) 58 | 59 | WEBDIR=$(HOME)/Site/unixuser.org/vnc2swf/ 60 | publish: pack 61 | mv $(WORKDIR)/$(DISTNAME).tar.gz $(WEBDIR) 62 | mv $(WORKDIR)/$(DISTNAME).zip $(WEBDIR) 63 | cp docs/*.html $(WEBDIR) 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pyvnc2swf 2 | 3 | Vnc2swf is a cross-platform screen recording tool for ShockWave Flash (swf), Flash Video (flv), MPEG and raw VNCRev and noVNC format. 4 | 5 | The noVNC output support is added by [TinyLab.org](http://tinylab.org), the [VPlayer (Tiny noVNC Player)](https://github.com/showdesk/showdesk.io) of [Showdesk](https://showdesk.github.io) can replay it. 6 | 7 | Here is the Python version, please get more information from its original homepage. 8 | 9 | * For documentation, see [docs/pyvnc2swf.html](http://www.unixuser.org/~euske/vnc2swf/pyvnc2swf.html). 10 | * Homepage: 11 | * Download: [pyvnc2swf-0.9.5.tar.gz](http://www.unixuser.org/~euske/vnc2swf/pyvnc2swf-0.9.5.tar.gz) 12 | * Discussion: 13 | 14 | **Update**: Development of vnc2swf is now superseded by its successor, [vnc2flv](http://www.unixuser.org/~euske/python/vnc2flv/index.html). (2009/10/03) 15 | 16 | ## Acknowledgements 17 | 18 | * Jesse Ruderman (Seekbar javascript code) 19 | * Radoslaw Grzanka (MemoryError bug fix) 20 | * Luis Fernando Kauer (VNC protocol error fix, cursor pseudo-encoding support, FLV support, lots of bugfixes) 21 | * Rajesh Menon (OSX assertion error fix) 22 | * Vincent Pelletier (MPEG encoding support) 23 | * Uchida Yasuo (Windows file bug fix) 24 | * Andy Leszczynski (MP3 and PyMedia bug fix) 25 | * David Fraser (audio recording with PyMedia) 26 | 27 | ## Bugs 28 | 29 | * Noises with non-multiple scaling (e.g. 0.7) 30 | * Ctrl-C at bad timings might cause the program abort. 31 | * Sometimes MPEGVideoStream crashes. (pymedia? - I couldn't replay.) 32 | * Timing issue (esp. notable in vnclog) 33 | 34 | ## TODOs 35 | 36 | * Audio support on FLV. 37 | * Neat GUI. 38 | * Authoring tool. (combining vnc2swf.py, edit.py and play.py) 39 | * Cursor shadow support. 40 | * Improve image scaling. (specify the scale ratio by size) 41 | * Screen snapshot tool (with no animation). 42 | * Distribution of Windows/Mac binaries. (py2exe, py2app) 43 | * Audio recording/replay with PyMedia. 44 | * Stop recording remotely. 45 | * FLV editing. 46 | 47 | ## Quickstart 48 | 49 | Use raw commands: 50 | 51 | $ x11vnc -quiet -cursor -viewonly -bg -localhost -nopw && ./vnc2swf.py -n -o out.swf :0 52 | 53 | $ tcpserver -vRHl0 localhost 10000 sh -c 'x11vnc -quiet -bg -nopw -viewonly -localhost -cursor -wait 10 -defer 10 >/dev/null 2>&1 && echo HTTP/1.0 200 OK && echo Content-Type: video/x-flv && echo && ./vnc2swf.py -n -t flv -o -' 54 | 55 | Use commands wrapper: 56 | 57 | $ tools/record.sh # Record the session 58 | 59 | $ tools/play.sh # Replay the session 60 | -------------------------------------------------------------------------------- /bin/recordwin.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ## 3 | ## recordwin.sh 4 | ## $Id: recordwin.sh,v 1.3 2008/11/16 02:39:40 euske Exp $ 5 | ## 6 | ## Quick recording script for UNIX. 7 | ## 8 | ## usage: 9 | ## recordwin.sh [-display disp] [-name winname] [-id winid] output.swf 10 | ## 11 | ## Requires: x11vnc, xwininfo, awk 12 | ## 13 | 14 | PYTHON=python 15 | VNC2SWF=pyvnc2swf/vnc2swf.py 16 | X11VNC=x11vnc 17 | XWININFO=xwininfo 18 | AWK=awk 19 | 20 | usage() { 21 | echo "usage: $0 [-all] [-display display] [-name windowname] [-id windowid] [-type filetype] outfile" 22 | exit 100 23 | } 24 | 25 | vncopts= 26 | xwopts= 27 | desktop= 28 | display="$DISPLAY" 29 | while [ $# -gt 1 ]; do 30 | case "$1" in 31 | -all|-a) desktop=1;; 32 | -name) shift; xwopts="$xwopts -name $1";; 33 | -id) shift; xwopts="$xwopts -id $1";; 34 | -display|-d) shift; display="$1"; xwopts="$xwopts -display $1";; 35 | -type|-t) shift; vncopts="$vncopts -t $1";; 36 | -*) usage;; 37 | esac 38 | shift 39 | done 40 | 41 | if [ $# -lt 1 ]; then usage; fi 42 | 43 | outfile="$1" 44 | if [ "X$desktop" = "X" ]; then 45 | info=`$XWININFO $xwopts 2>/dev/null` 46 | if [ "X$info" = "X" ]; then 47 | echo "Window $xwopts not found!" 48 | exit 2 49 | fi 50 | geometry="-C `echo "$info" | 51 | $AWK '/Absolute upper-left X:/{x=$4} 52 | /Absolute upper-left Y:/{y=$4} 53 | /Width:/{w=$2} /Height:/{h=$2} 54 | END {printf "%dx%d+%d+%d",w,h,x,y}' `" 55 | echo $geometry 56 | fi 57 | 58 | # launch x11vnc and vnc2swf 59 | $X11VNC -quiet -bg -nopw -display "$display" -viewonly -localhost -cursor -wait 10 -defer 10 && 60 | $PYTHON $VNC2SWF -n $vncopts -o "$outfile" $geometry 61 | -------------------------------------------------------------------------------- /docs/changes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | vnc2swf - Changes 7 | 8 | 9 | 10 |

vnc2swf
11 | Changes

12 | 19 |
20 | 21 | 22 |

History

23 | 24 |

pyvnc2swf-0.9.5 (Nov 15, 2008)

25 |
    26 |
  • FLV direct recording supported. 27 |
28 | 29 |

pyvnc2swf-0.9.3 (Apr 25, 2007)

30 |
    31 |
  • Replaced crippled_des.py with d3des.py. (License issue) 32 |
33 | 34 |

pyvnc2swf-0.9.2 (Mar 31, 2007)

35 |
    36 |
  • FLV support added. (Thanks to Luis Fernando) 37 |
38 | 39 |

pyvnc2swf-0.9.1 (Nov 3, 2006)

40 |
    41 |
  • GUI-related bugfixes. 42 |
43 | 44 |

pyvnc2swf-0.9.0 (Oct 29, 2006)

45 |
    46 |
  • More intuitive UI. 47 |
  • Fixed PyMedia related bugs. 48 |
  • Fixed MP3 related bugs. 49 |
  • Disable the movie scaling by default. 50 |
  • A password file must be encrypted. 51 |
52 | 53 |

pyvnc2swf-0.8.2 (Feb 9, 2006)

54 |
    55 |
  • mp3skip can be specified with seconds. 56 |
  • Improved error handling. 57 |
  • Cursor bug is fixed. 58 |
59 | 60 |

pyvnc2swf-0.8.1 (Nov 28, 2005)

61 |
    62 |
  • MPEG encoding is supported. (Thanks to Vincent Pelletier) 63 |
  • Problem with RealVNC is fixed (Thanks to Andy Bueler) 64 |
  • Cursor color bug and vnc conversion bug is fixed (Thanks to Luis Fernando) 65 |
66 | 67 |

pyvnc2swf-0.8.0 (Nov 20, 2005)

68 |
    69 |
  • Cursor pseudo encoding is supported. Now it captures the mouse cursor fine on OSX. 70 | (Thanks to Luis Fernando) 71 |
  • Vnc protocol errors when using TightVNC or UltraVNC is fixed. 72 |
  • MPEGVideoStream is added but not tested. 73 |
74 | 75 |

pyvnc2swf-0.7.2 (Nov 11, 2005)

76 |
    77 |
  • "RFB illegal encoding" bug is fixed. (Thanks to David Karnowski for pointing this out) 78 |
  • recordwin script is added. 79 |
  • More efficient recording when clipping area is specified. 80 |
81 | 82 |

vnc2swf-0.5.0 (Oct 29, 2005)

83 |
    84 |
  • Now C version of vnc2swf no longer depends on Ming library. 85 |
86 | 87 |

pyvnc2swf-0.7.1 (Oct 27, 2005)

88 |
    89 |
  • MemoryError bug on Windows is fixed. 90 | (Thanks to Radoslaw Grzanka) 91 |
92 | 93 |

pyvnc2swf-0.7.0 (Oct 23, 2005)

94 |
    95 |
  • Documentation and seekbar function are added. 96 | Thanks to Jesse Ruderman, who contributed the seekbar code. 97 |
98 | 99 |

pyvnc2swf-0.6.4 (Aug 23, 2005)

100 |
    101 |
  • pyvnc2swf-0.6.4 was released. Scaling and frame resampling are supported. 102 |
103 | 104 |

pyvnc2swf-0.6.3 (Aug 5, 2005)

105 |
    106 |
  • pyvnc2swf-0.6.3 was released. MP3 seeking and small bugfix. 107 |
108 | 109 |

pyvnc2swf-0.6.2 (Jul 23, 2005)

110 |
    111 |
  • pyvnc2swf-0.6.2 was released. Small bugfix. 112 |
113 | 114 |

pyvnc2swf-0.6.1 (Jul 22, 2005)

115 |
    116 |
  • pyvnc2swf-0.6.1 was released. Subprocess handling improved. 117 |
118 | 119 |

pyvnc2swf-0.6 (Jul 21, 2005)

120 |
    121 |
  • pyvnc2swf-0.6 was released. Several fixes for memory consumption problems. 122 |
123 | 124 |

pyvnc2swf-0.5 (Jul 17, 2005)

125 |
    126 |
  • pyvnc2swf-0.5 was released. Now it supports non-unix platforms, including OSX and Windows. 127 |
128 | 129 |

edit_vnc2swf-0.3 (Feb 4, 2005)

130 |
    131 |
  • Edit_vnc2swf-0.3 was released. Simple viewer is supported. 132 |
133 | 134 |

edit_vnc2swf-0.2 (Jan 29, 2005)

135 |
    136 |
  • Edit_vnc2swf was released. This will be integrated with vnc2swf package in future. 137 |
  • edit_vnc2swf (Jan 29, 2005). 138 |
139 | 140 |

vnc2swf-0.4.2 (May 19, 2004)

141 |
    142 |
  • Segmentation Fault bug when attaching mp3 is fixed. 143 |
144 | 145 |

vnc2swf-0.4.1 (May 9, 2004)

146 |
    147 |
  • A MacOS X Bug is fixed (Yellowish screen). 148 |
149 | 150 |

vnc2swf-0.4 (Apr. 3, 2004)

151 |
    152 |
  • Be able to record an existing desktop (with x11vnc). 153 |
  • Partial recording support added. 154 |
  • Rewinding support added. 155 |
  • Status display support added. 156 |
157 | 158 |

vnc2swf-0.3 (Oct. 25, 2003)

159 |
    160 |
  • Based on VNC viewer 3.3.7. 161 |
  • ./configure support. 162 |
  • Sound file attachment support. (Contributed by Tim Jansen <tim at tjansen dot de>) 163 |
  • Pause/Restart recording support. 164 |
  • Another recording algorithm. 165 |
166 | 167 | 168 |

Older Versions

169 | 170 |

Pyvnc2swf

171 | 190 | 191 |

Vnc2swf (C version)

192 | 203 | 204 |
205 |

206 | 207 | Last Modified: Sun Nov 16 11:30:58 JST 2008 208 | 209 |

Yusuke Shinyama
210 | 211 | 212 | -------------------------------------------------------------------------------- /docs/index-j.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinyclub/pyvnc2swf/fef3e6075f67954426e199a1f10902fa0e65c03a/docs/index-j.html -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | vnc2swf - Screen Recorder 7 | 8 | 9 | 10 |

11 | vnc2swf
12 | Screen Recorder 13 |

14 |

19 | Vnc2swf is a cross-platform screen recording tool for ShockWave Flash (swf) or Flash Video (flv) format. 20 | 21 |

Download:

22 |

23 | 24 | 25 | 26 | 30 | 31 | 32 | 33 | 36 | 37 |
Python version:(Nov 15, 2008) pyvnc2swf-0.9.5tar.gz or 27 | zip or 28 | RPM/Win32 29 |
C version:(Oct 29, 2005) vnc2swf-0.5.0tar.gz or 34 | RPM 35 |   What is the difference?
38 | 39 | 40 | 41 |

Questions or Comments:

42 |

43 | Write to 44 | vnc2swf-users@lists.sourceforge.net. 45 | (Here is the archive 46 | and how to subscribe) 47 | 48 |

49 | Recent changes 50 | 51 |


52 |

Sample Movie

53 | 62 | 63 | 64 |
65 |

Frequently Asked Questions

66 | 67 | 68 |

What's the difference between Python version and C version?

69 |

70 | Vnc2swf comes with two different implementations: 71 |

    72 |
  • Python version (pyvnc2swf): 73 |
      74 |
    • Runs on many platforms, including Linux, FreeBSD, Solaris, Mac OS X, and Windows. 75 |
    • Has more functions (e.g. editing, adding a seekbar). 76 |
    • Is being actively developed. 77 |
    78 |
  • C version (vnc2swf): 79 |
      80 |
    • Only runs on platforms which support X11 (i.e. Unix, Linux or Mac OS X). 81 |
      NOTE: The latest version no longer needs Ming. 82 |
    • Has a vnc viewer. 83 |
    • Simple and lightweight. 84 |
    85 |
86 | 87 |

88 | For both versions, you need to install at least one VNC server: 89 |

98 | 99 |

100 | Additionally, for Python version, you need Python and Pygame package. 101 | For more information, see pyvnc2swf page. 102 | 103 |

Output movies are garbled!

104 |

105 | Several users reported they had a garbled movie when they used 106 | ATI binary drivers for the X server. Please switch to the open source one. 107 | 108 | 109 |

Could you add this new feature?

110 |

111 | Recently I've received lots of feature requests. 112 | Unfortunately, some of them are hard to implement because 113 | of the nature of this software, including: 114 |

    115 |
  • Recording mouse click. (VNC doesn't send mouse click information) 116 |
  • Recording key stroke. (VNC doesn't send this, too) 117 |
  • Supporting OGG format instead of MP3. (SWF file doesn't support this.) 118 |
119 | 120 | 121 |
122 |

License

123 |

Vnc2swf comes with ABSOLUTELY NO WARRANTY. 124 | This software is distributed under the GNU General Public License. 125 | 126 | 127 |


128 |

Author

129 |

Yusuke Shinyama 130 | (For questions or comments, please write to 131 | the mailing list 132 | so that we can share them!) 133 | 134 | 135 |


136 |

Links

137 | 149 | 150 | 151 |
152 |

153 | 154 | Last Modified: Sun Nov 16 11:55:13 JST 2008 155 | 156 |

Yusuke Shinyama
157 | 158 | 159 | -------------------------------------------------------------------------------- /docs/pyvnc2swf-j.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinyclub/pyvnc2swf/fef3e6075f67954426e199a1f10902fa0e65c03a/docs/pyvnc2swf-j.html -------------------------------------------------------------------------------- /docs/pyvnc2swf.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | pyvnc2swf 4 | 5 | 6 | 7 |

pyvnc2swf

8 |

9 | back 10 | [Japanese] 11 |

12 | Homepage: http://www.unixuser.org/~euske/vnc2swf/
13 | Discussion: http://lists.sourceforge.net/lists/listinfo/vnc2swf-users 14 |

15 | $Id: pyvnc2swf.html,v 1.2 2008/11/16 02:39:40 euske Exp $ 16 |


17 |

18 | Pyvnc2swf is a cross-platform screen recording tool. 19 | It captures screen motion through VNC protocol and generates a Shockwave Flash (SWF) movie. 20 | Pyvnc2swf suite comes with three Python programs: 21 |

    22 |
  • vnc2swf.py - Recorder 23 |
  • edit.py - Movie editor 24 | (This is NOT a general SWF file editor. It only supports movies generated by vnc2swf.) 25 |
  • play.py - Simple movie viewer 26 |
27 | 28 |

29 | For questions, please read the FAQ and 30 | list archives 31 | before sending me emails. 32 | 33 |

34 | Terms and Conditions: 35 | Pyvnc2swf comes with ABSOLUTELY NO WARRANTY. 36 | This software is distributed under the GNU General Public License. 37 | 38 |


39 | 40 |

Installation

41 | 42 |

In all platforms, the following packages are required: 43 |

    44 |
  • Python (2.4 or above) 45 |
  • Pygame (1.6 or above) 46 |
  • Optional: PyMedia (1.3.5 or above - required for mpeg encoding) 47 |
48 |

49 | In most Linux distros, these packages are readily available. 50 | In Mac OS X, you would need an additional 51 | OS X build of Pygame package. 52 |

53 | Also, you need at least one VNC server: 54 |

    55 |
  • RealVNC or TightVNC (Unix, Linux or Windows) 56 |
  • x11vnc (Unix/Linux for recording an existing desktop) 57 |
  • OSXVnc (Mac OS X) 58 |
  • GNOME Vino 59 | (This is not an independent package, but if you have GNOME desktop environment 60 | which supports remote desktop functionality, you have it.) 61 |
62 | 63 | 64 |
65 |

Recording

66 |

67 | vnc2swf.py program 68 | captures a VNC sessions and records it in either SWF or VNCLog format. 69 | This is a VNC client and communicates directly with a VNC server. 70 | A user need to start a VNC server in advance. 71 |

72 | vnc2swf.py runs in two different modes: 73 | GUI (Graphical User Interface) mode and CLI (Command Line Interface) mode. 74 | In the GUI mode, start recording by clicking the "Start" button. 75 | Then choose the "Save as..." command from the "File" menu to save 76 | the recorded movie to a file. 77 | In the CLI mode, a user needs to specify the output filename from command line. 78 | Recording is started immediately. In both modes, a user is 79 | prompted for a VNC password if the server requires authentication 80 | (and unless the user doesn't specify the password file). 81 | In the CLI mode, hit Control-C to stop recording. 82 | After finishing recording, it generates two files with the specified name: 83 | a .swf and .html file. 84 | The .html file contains an HTML tag and a javascript code 85 | to provide seek bar function. 86 |

87 | A user can choose three different methods to encode movie image: 88 | "flv", "swf5", "swf7", "mpeg" (PyMedia required), 89 | or "vnc". 90 | The first swf5 encoding (default) provides a reasonable movie size. 91 | The second encoding method, swf7 provides a smaller SWF movie. 92 | This is, however, not recommended to use within vnc2swf.py for 93 | two reasons: This type of encoding is only supported by Flash Player 94 | version 7 or newer. Also, generating a movie with on-the-fly 95 | swf7 encoding is slower so you might experience frame dropping. 96 | Actually, you can convert a swf5-encoded movie into swf7-encoded 97 | one after recording by using edit.py, so anyway you don't need to 98 | use this method when recording. The third encoding method is vnc. 99 | This method generates a .vnc (VNCLog) file, which is compatible with 100 | vncrec output file. 101 | You can convert it to a SWF movie with edit.py. 102 | A .vnc file is not a SWF movie by itself, but its encoding is the fastest. 103 |

104 | NOTE: 105 | Unlike the C version, vnc2swf.py doesn't handle any 106 | user interaction. If you want to control the server's desktop, 107 | you need to launch vncviewer or its equivalent separately. 108 | 109 |

Syntax

110 |
    111 |
  • (GUI) $ vnc2swf.py [-o filename] [options] [host[:display] [port]] 112 |
  • (CLI) $ vnc2swf.py -n -o filename [options] [host[:display] [port]] 113 |
114 | 115 |

Example

116 |
117 | (Record a virtual screen)
118 | $ vncserver -geometry 640x480
119 | $ vnc2swf.py -o out.swf localhost:1
120 | 
121 | (Record an existing screen)
122 | $ x11vnc -localhost -viewonly -wait 10 -defer 10 -bg
123 | $ vnc2swf.py -o out.swf localhost:0
124 | 
125 | (Record a remote screen)
126 | $ vnc2swf.py -n -o out.flv vnc.example.com:1
127 | 
128 | 
129 | 130 |

If you're using x11vnc, see also recordwin. 131 | This is a convenient script to record a particular window. 132 | 133 |

Recording Tips

134 |
    135 |
  • Use as small screen size as possible, or try to specify the smallest clipping rectangle. 136 | With a large screen, VNC's screen polling gets slower and 137 | vnc2swf also gets slower for converting bigger images. 138 | Using a small screen also helps reducing the movie size. 139 |
  • Reducing the framerate sometimes helps reducing the movie size. 140 | If the generated movie is still very big, try resampling frames or 141 | scaling the image with edit.py. 142 |
  • To record a long movie (more then 20min.), the only available option is 143 | to use flv (or mpeg if you have PyMedia). 144 | swf5 or swf7 files have a limitation and 145 | lcan have up to 16000 frames, which is roughly 22 minutes. 146 |
  • If you experience frame dropping, try changing the server options for screen polling (if any). 147 | If you're using x11vnc, try adding -wait 10 -defer 10. 148 |
  • When recording flv movie, you can dump a live stream output 149 | to stdout by speficing '-' as a filename. But currently I have no idea 150 | how to use. (broadcasting your desktop on the web?) 151 |
152 | 153 |

Options

154 |
155 |
-n 156 |
Console mode (no GUI). 157 |

158 |

-o outputfile 159 |
Specifies the output filename. This option is required in CLI mode. 160 | In GUI mode, when not specified it prompts the user for the filename. 161 | The movie encoding type is usually inferred from the filename, so 162 | the filename should end with either ".swf" or ".vnc". 163 | Otherwise, the user need to specify the output movie encoding with -t option 164 | (see below.) 165 |

166 |

-C clipping 167 |
Specifies the clipping rectangle. 168 | The geometry must be as form of "widthxheight+left+top" 169 | (e.g. "400x300+120+0"). 170 | Unlike other X11 applications, all rectangular components are required. Negative values are not supported. 171 |

172 |

-r framerate 173 |
Specifies the framerate in fps. (default=12.0) 174 |

175 |

-t encodingtype 176 |
Specifies the output movie encoding method 177 | ("flv", "mpeg", "swf5", "swf7" or "vnc"). 178 | When omitted, the encoding type is automatically inferred from the filename 179 | (*.swf = swf5, *.vnc = vnc). 180 |

181 |

-N 182 |
Disables cursor pseudo-encoding. Pyvnc2swf normally tries to use 183 | cursor pseudo-encoding to capture a mouse cursor position 184 | so that a cursor can be moved separately from a screen image 185 | and it reduces the movie size. However this might not work with 186 | some vnc servers. This option can be used for disabling the function. 187 |

188 |

-P passwdfile 189 |
Specifies a password file. If specified, its content is automatically 190 | loaded and supplied as password when a VNC server requires it. 191 | A password file contains an encrypted password string and 192 | can be created with vncpasswd. A user can directly specify 193 | ~/.vnc/passwd, which normally contains the password 194 | for the local vnc server. 195 |

196 |

-e vncencodings 197 |
Specifies the preferred encodings for VNC image transfer 198 | (this is different from movie encoding). 199 | Normally you don't need to change this option. 200 | Encodings are comma-separated integers (default="5,4,0"). 201 | Changing encodings might improve recording performance. 202 |

203 |

-S subprocess (Supported on Un*x only, Python 2.4 or above is required) 204 |
Set a command to run during recording. 205 | This option is useful for recording voice with a separated program. 206 | A command line is a space-separated sequence of arguments 207 | which are passed to a child process like a usual shell command line. 208 | However the command line is not passed to a shell and the arguments are directly passed 209 | to the child process without any substitution. A child process is started 210 | immediately after recording starts and sent SIGINT after recording finishes. 211 |

212 |

-d 213 |
Increases debug level. 214 |

215 |

216 | 217 | 218 |
219 |

Editing

220 |

221 | edit.py program is for editing or reorganizing one or multiple movies 222 | generated by vnc2swf.py. This program also supports converting 223 | a .vnc file into .swf movie, changing the encoding method (swf5->swf7), 224 | attaching MP3 audio file to a movie, extracting 225 | images from a movie and resampling/scaling/clipping a movie image. 226 |

227 | edit.py currently supports only command line interface. 228 | The user must give one output filename and one or more input filename(s). 229 | Input movies are concatenated sequentially (in the specified order) 230 | and the desired effects are applied. 231 |

232 | 233 |

Syntax

234 |

235 | $ edit.py -o outfile.swf [options] infile ... 236 | 237 |

Example

238 |
239 | (Convert .vnc file into .swf with compressed video encoding)
240 | $ edit.py -o out.swf -c -t swf7 input.vnc
241 | 
242 | (Attach an mp3 file to .swf)
243 | $ edit.py -o out.swf -a voice.mp3 in.swf
244 | 
245 | (Concatenate two movies and extract the frames into another movie)
246 | $ edit.py -o out.swf -f 100-200,350- movie1.swf movie2.swf
247 | 
248 | (Clip the top left area of the movie and shrink it to half the size)
249 | $ edit.py -o small.swf -C 320x240+0+0 -s 0.5 in.swf
250 | 
251 | (Convert .swf into MPEG)
252 | $ edit.py -o out.mpg input.swf
253 | 
254 | (Convert .swf into .flv)
255 | $ edit.py -o out.flv input.swf
256 | 
257 | 258 | 259 |

Options

260 |
261 |
-o outputfile 262 |
Specifies the output filename. This option is always required. 263 |

264 |

-c 265 |
Compress a movie with zlib. Compressions is supported for 266 | both (swf5 and swf7) types of movies, but 267 | usually it's most effective when applied to swf7-encoded movies. 268 |

269 |

-t encodingtype 270 |
Specifies the encoding method for an output movie. 271 | When this option is not specified, the encoding type is inferred 272 | from the output filename extention. 273 |

274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 |
TypeExtensionDescription
swf5.swfSWF movie (default)
swf7SWF movie with stream video encoding support
flv.flvFLV movie
mpeg.mpgMPEG movie (requires PyMedia)
bmp.bmpBMP image sequence
png.pngPNG image sequence
283 |

284 | Note that swf7 encoding is supported only with Flash Player version 7 or newer. 285 |

286 |

-f frames or 287 |
-F frames 288 |
Specifies a sequence of frames presented in the output movie. 289 | When omitted, all frames are presented in the original order (default). 290 | Frames can be specified with a comma-separated list of integers. 291 | A range of integers can be also specified by using - (hyphen) sign. 292 | For example: 10,200,300-400 specifies the frames whose number is 10 and 200, 293 | plus every frame between frame 300 and 400. The beginning (or ending) frame number can be omitted 294 | (e.g. -100 or 300-). In this case, the first (or last) frame number is used 295 | as the other end of the range. 296 |

297 | -F option and -f option is same except that -F doesn't 298 | chop the audio while -f does. When you're putting audio on the movie, if 299 | you use -f and -a option at the same time, it chops an mp3 file 300 | according to the selected frames. However, when you want to cast a continuous sound (such as music) 301 | onto a whole movie, this might not be the desired effect. In such a case, use -F 302 | instead of -f. 303 |

304 |

-a mp3file 305 |
Attaches mp3 file(s) to the movie. (Adding audio to FLV format is currently not supported.) 306 | Multiple mp3 files are concatenated in the specified order. 307 |
308 | NOTE: When specifying multiple mp3 files, 309 | make sure every file has the same bitrate as the first one. 310 | And do NOT use "variable bitrate" (VBR) mp3 files, 311 | as the SWF format doesn't support them! 312 |

313 |

-s scaling 314 |
Rescale the movie image with a specified ratio 315 | which is given as a fraction. 316 |
317 | NOTE: There will be noises with non-multiple scaling (e.g. 0.7). 318 |

319 |

-C clipping 320 |
Clips the movie into a specified rectangle. 321 | The geometry must be as form of "widthxheight+left+top" 322 | (e.g. "400x300+120+0"). 323 |

324 |

-K keyinterval 325 |
Insert a keyframe in every N frames. 326 | Keyframes work as hints for Flash Player and are useful for 327 | seeking a frame within a long movie. 328 | When the number of total frames exceeds 10,000, inserting 329 | keyframes in every 500 frames (-K 500) is recommended. 330 |

331 |

-r framerate 332 |
Changes the frame rate of the movie. This option itself doesn't do any frame resampling 333 | and simply changes the movie speed. When this option is omitted, edit.py tries 334 | to keep the original frame rate in the output movie. 335 |

336 |

-R resampleframes 337 |
Resample (or "thin down") a movie by picking one from every N frames. 338 |

339 |

-S skipmp3frames 340 |
Skip the first N mp3-frames of the mp3 file. 341 | When 's' is appended to the number (such as '1.0s'), 342 | it indicates the number of seconds instead of frames. 343 | This option is useful when there is a time lag between a 344 | recorded image and audio. 345 |

346 |

-B blocksize 347 |
Sets the blocksize for swf7 or flv encoding method (default=32). 348 | This must be a multiple of 16. 349 |

350 |

-b 351 |
Suppress a seekbar in a generated html file. 352 |

353 |

-l 354 |
Disables movie loop in a generated html file. 355 |

356 |

-d 357 |
Increases debug level. 358 |

359 |

360 | 361 |
362 |
363 |

Previewing

364 |

365 | play.py is a simple player for a .swf or .vnc file. 366 | This program might be useful for spotting the right frame number in a recorded movie. 367 | However its speed is awkward and audio output is not supported. 368 | It only supports vnc2swf-generated files and cannot play general SWF movies. 369 |

370 | The player accepts the following keys: 371 |

    372 |
  • Space / Enter / mouse click: Toggle Play/Stop. 373 |
  • Left / Right / dragging a seek bar: Skip frames. 374 |
  • "q" / Escape: Quit. 375 |
  • "s": Take a snapshot. The image is saved as "inputfile-frameno.bmp". 376 |
377 | 378 |

Syntax

379 |

380 | $ play.py [options] moviefile ... 381 | 382 |

Options

383 |
384 |
-r framerate 385 |
Specifies the framerate to play. 386 |

387 |

-s scaling 388 |
Specifies the scaling ratio with a fraction. 389 |

390 |

-C clipping 391 |
Specifies the clipping rectangle. 392 | The geometry must be as form of "widthxheight+left+top" 393 | (e.g. "400x300+120+0"). 394 |

395 |

-d 396 |
Increases debug level. 397 |

398 |

399 | 400 |
401 |
402 |

Frequently Asked Questions

403 |
404 |
I found weird artifacts in a recorded movie. How to fix it? 405 |
Probably you played the SWF file directly. 406 | A SWF movie should be played in the exactly same size as it is recorded. 407 | Use an HTML file generated by vnc2swf.py or edit.py. 408 |

409 |

How to make a movie with audio? 410 |
Record your audio separately and encode it with an MP3 encoder 411 | like Lame. 412 | Then combine them with edit.py. 413 |

414 |

A browser freezes when I try to seek within a movie. 415 |
Put keyframes in every hundreds frame. Try -K option in edit.py. 416 | Normally putting keyframes in every 500 frames (-K 500) gives a reasonable result. 417 |

418 |

419 | 420 | 421 |
422 |

Structure of the Program

423 |

424 | For those who are interested in learning or extending the program, 425 | I drew 426 | a simple figure to explain how data goes between objects within pyvnc2swf. 427 | 428 | 429 |


430 |
431 | Yusuke Shinyama 432 | <yusuke at cs dot nyu dot edu> 433 |
434 | 435 | 436 | -------------------------------------------------------------------------------- /docs/recordwin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | recordwin 6 | 7 | 8 | 9 |

recordwin

10 |

A wrapper program for pyvnc2swf and x11vnc. 11 |

12 | $Id: recordwin.html,v 1.2 2008/11/16 02:39:40 euske Exp $ 13 |


14 | 15 |

Syntax

16 |
17 | recordwin.sh 18 | [-all] 19 | [-display displayname] 20 | [-name windowtitle] 21 | [-id windowid] 22 | [-type filetype] 23 | outputfile 24 | 25 |
26 | 27 |

Example

28 |
29 | (Manually specify the window to record)
30 | $ recordwin.sh out.swf
31 | 
32 | (Record the window titled "login@giko")
33 | $ recordwin.sh -name "login@giko" out.flv
34 | 
35 | 36 |

Description

37 |

38 | recordwin allows you to record a specific window in 39 | the current screen instead of the entire display. This is a 40 | wrapper program for vnc2swf.py and 41 | x11vnc. First it invokes xwininfo 42 | program to get the position of a target window. If no option is 43 | given, a user is prompted to specify a window with a cross (+) 44 | cursor. Then it runs x11vnc in background and starts 45 | vnc2swf.py with a proper option to record only the 46 | window you specified. With the option -all specified, 47 | the entire screen is recorded. 48 | 49 |

50 | Security consideration: 51 | The user must be aware that recordwin will open up 52 | x11vnc to accept any local client that comes first. 53 | Although this client is only allowed to peek the current screen, this can be 54 | potentially security threat. So don't use this if your desktop machine allows 55 | other users to login. 56 | 57 |

Bugs

58 |
    59 |
  • Error handling is poor. 60 |
  • To prevent a possibly unwanted access, recordwin 61 | should have set a temporaly password for x11vnc. 62 |
63 | 64 |

See Also

65 | 69 | 70 |
71 |
Yusuke Shinyama
72 | <yusuke at cs dot nyu dot edu> 73 | 74 | 75 | -------------------------------------------------------------------------------- /icons/player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinyclub/pyvnc2swf/fef3e6075f67954426e199a1f10902fa0e65c03a/icons/player.png -------------------------------------------------------------------------------- /icons/recorder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinyclub/pyvnc2swf/fef3e6075f67954426e199a1f10902fa0e65c03a/icons/recorder.png -------------------------------------------------------------------------------- /pyvnc2swf/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile (only for maintainance purpose) 2 | # $Id: Makefile,v 1.1 2008/11/15 10:07:09 euske Exp $ 3 | # 4 | # Copyright (C) 2005 by Yusuke Shinyama (yusuke at cs . nyu . edu) 5 | # All Rights Reserved. 6 | # 7 | # This is free software; you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation; either version 2 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This software is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this software; if not, write to the Free Software 19 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 20 | # USA. 21 | # 22 | 23 | all: 24 | 25 | clean: 26 | -rm *.pyc *~ '.#*' .DS_Store 27 | 28 | test: 29 | x11vnc -quiet -localhost -viewonly -nopw -bg 30 | ./vnc2swf.py -n -o out.swf localhost:0 31 | -------------------------------------------------------------------------------- /pyvnc2swf/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinyclub/pyvnc2swf/fef3e6075f67954426e199a1f10902fa0e65c03a/pyvnc2swf/__init__.py -------------------------------------------------------------------------------- /pyvnc2swf/d3des.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ## 3 | ## pyvnc2swf - d3des.py 4 | ## 5 | ## $Id: d3des.py,v 1.1 2007/04/25 16:55:25 euske Exp $ 6 | ## 7 | ## Copyright (C) 2007 by Yusuke Shinyama (yusuke at cs . nyu . edu) 8 | ## All Rights Reserved. 9 | ## 10 | ## This is free software; you can redistribute it and/or modify 11 | ## it under the terms of the GNU General Public License as published by 12 | ## the Free Software Foundation; either version 2 of the License, or 13 | ## (at your option) any later version. 14 | ## 15 | ## This software is distributed in the hope that it will be useful, 16 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | ## GNU General Public License for more details. 19 | ## 20 | ## You should have received a copy of the GNU General Public License 21 | ## along with this software; if not, write to the Free Software 22 | ## Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 23 | ## USA. 24 | ## 25 | 26 | # The following code is derived from vncviewer/rfb/d3des.c (GPL). 27 | 28 | # This is D3DES (V5.09) by Richard Outerbridge with the double and 29 | # triple-length support removed for use in VNC. Also the bytebit[] array 30 | # has been reversed so that the most significant bit in each byte of the 31 | # key is ignored, not the least significant. 32 | # 33 | # These changes are: 34 | # Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved. 35 | # 36 | # This software is distributed in the hope that it will be useful, 37 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 38 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 39 | # 40 | # 41 | # D3DES (V5.09) - 42 | # 43 | # A portable, public domain, version of the Data Encryption Standard. 44 | # 45 | # Written with Symantec's THINK (Lightspeed) C by Richard Outerbridge. 46 | # Thanks to: Dan Hoey for his excellent Initial and Inverse permutation 47 | # code; Jim Gillogly & Phil Karn for the DES key schedule code; Dennis 48 | # Ferguson, Eric Young and Dana How for comparing notes; and Ray Lau, 49 | # for humouring me on. 50 | # 51 | # Copyright (c) 1988,1989,1990,1991,1992 by Richard Outerbridge. 52 | # (GEnie : OUTER; CIS : [71755,204]) Graven Imagery, 1992. 53 | # 54 | 55 | from struct import pack, unpack 56 | 57 | bytebit = [ 01, 02, 04, 010, 020, 040, 0100, 0200 ] 58 | 59 | bigbyte = [ 60 | 0x800000L, 0x400000L, 0x200000L, 0x100000L, 61 | 0x80000L, 0x40000L, 0x20000L, 0x10000L, 62 | 0x8000L, 0x4000L, 0x2000L, 0x1000L, 63 | 0x800L, 0x400L, 0x200L, 0x100L, 64 | 0x80L, 0x40L, 0x20L, 0x10L, 65 | 0x8L, 0x4L, 0x2L, 0x1L 66 | ] 67 | 68 | # Use the key schedule specified in the Standard (ANSI X3.92-1981). 69 | 70 | pc1 = [ 71 | 56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17, 72 | 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 73 | 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 74 | 13, 5, 60, 52, 44, 36, 28, 20, 12, 4, 27, 19, 11, 3 75 | ] 76 | 77 | totrot = [ 1,2,4,6,8,10,12,14,15,17,19,21,23,25,27,28 ] 78 | 79 | pc2 = [ 80 | 13, 16, 10, 23, 0, 4, 2, 27, 14, 5, 20, 9, 81 | 22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1, 82 | 40, 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47, 83 | 43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31 84 | ] 85 | 86 | def deskey(key, decrypt): # Thanks to James Gillogly & Phil Karn! 87 | key = unpack('8B', key) 88 | 89 | pc1m = [0]*56 90 | pcr = [0]*56 91 | kn = [0L]*32 92 | 93 | for j in range(56): 94 | l = pc1[j] 95 | m = l & 07 96 | if key[l >> 3] & bytebit[m]: 97 | pc1m[j] = 1 98 | else: 99 | pc1m[j] = 0 100 | 101 | for i in range(16): 102 | if decrypt: 103 | m = (15 - i) << 1 104 | else: 105 | m = i << 1 106 | n = m + 1 107 | kn[m] = kn[n] = 0L 108 | for j in range(28): 109 | l = j + totrot[i] 110 | if l < 28: 111 | pcr[j] = pc1m[l] 112 | else: 113 | pcr[j] = pc1m[l - 28] 114 | for j in range(28, 56): 115 | l = j + totrot[i] 116 | if l < 56: 117 | pcr[j] = pc1m[l] 118 | else: 119 | pcr[j] = pc1m[l - 28] 120 | for j in range(24): 121 | if pcr[pc2[j]]: 122 | kn[m] |= bigbyte[j] 123 | if pcr[pc2[j+24]]: 124 | kn[n] |= bigbyte[j] 125 | 126 | return cookey(kn) 127 | 128 | def cookey(raw): 129 | key = [] 130 | for i in range(0, 32, 2): 131 | (raw0, raw1) = (raw[i], raw[i+1]) 132 | k = (raw0 & 0x00fc0000L) << 6 133 | k |= (raw0 & 0x00000fc0L) << 10 134 | k |= (raw1 & 0x00fc0000L) >> 10 135 | k |= (raw1 & 0x00000fc0L) >> 6 136 | key.append(k) 137 | k = (raw0 & 0x0003f000L) << 12 138 | k |= (raw0 & 0x0000003fL) << 16 139 | k |= (raw1 & 0x0003f000L) >> 4 140 | k |= (raw1 & 0x0000003fL) 141 | key.append(k) 142 | return key 143 | 144 | SP1 = [ 145 | 0x01010400L, 0x00000000L, 0x00010000L, 0x01010404L, 146 | 0x01010004L, 0x00010404L, 0x00000004L, 0x00010000L, 147 | 0x00000400L, 0x01010400L, 0x01010404L, 0x00000400L, 148 | 0x01000404L, 0x01010004L, 0x01000000L, 0x00000004L, 149 | 0x00000404L, 0x01000400L, 0x01000400L, 0x00010400L, 150 | 0x00010400L, 0x01010000L, 0x01010000L, 0x01000404L, 151 | 0x00010004L, 0x01000004L, 0x01000004L, 0x00010004L, 152 | 0x00000000L, 0x00000404L, 0x00010404L, 0x01000000L, 153 | 0x00010000L, 0x01010404L, 0x00000004L, 0x01010000L, 154 | 0x01010400L, 0x01000000L, 0x01000000L, 0x00000400L, 155 | 0x01010004L, 0x00010000L, 0x00010400L, 0x01000004L, 156 | 0x00000400L, 0x00000004L, 0x01000404L, 0x00010404L, 157 | 0x01010404L, 0x00010004L, 0x01010000L, 0x01000404L, 158 | 0x01000004L, 0x00000404L, 0x00010404L, 0x01010400L, 159 | 0x00000404L, 0x01000400L, 0x01000400L, 0x00000000L, 160 | 0x00010004L, 0x00010400L, 0x00000000L, 0x01010004L 161 | ] 162 | 163 | SP2 = [ 164 | 0x80108020L, 0x80008000L, 0x00008000L, 0x00108020L, 165 | 0x00100000L, 0x00000020L, 0x80100020L, 0x80008020L, 166 | 0x80000020L, 0x80108020L, 0x80108000L, 0x80000000L, 167 | 0x80008000L, 0x00100000L, 0x00000020L, 0x80100020L, 168 | 0x00108000L, 0x00100020L, 0x80008020L, 0x00000000L, 169 | 0x80000000L, 0x00008000L, 0x00108020L, 0x80100000L, 170 | 0x00100020L, 0x80000020L, 0x00000000L, 0x00108000L, 171 | 0x00008020L, 0x80108000L, 0x80100000L, 0x00008020L, 172 | 0x00000000L, 0x00108020L, 0x80100020L, 0x00100000L, 173 | 0x80008020L, 0x80100000L, 0x80108000L, 0x00008000L, 174 | 0x80100000L, 0x80008000L, 0x00000020L, 0x80108020L, 175 | 0x00108020L, 0x00000020L, 0x00008000L, 0x80000000L, 176 | 0x00008020L, 0x80108000L, 0x00100000L, 0x80000020L, 177 | 0x00100020L, 0x80008020L, 0x80000020L, 0x00100020L, 178 | 0x00108000L, 0x00000000L, 0x80008000L, 0x00008020L, 179 | 0x80000000L, 0x80100020L, 0x80108020L, 0x00108000L 180 | ] 181 | 182 | SP3 = [ 183 | 0x00000208L, 0x08020200L, 0x00000000L, 0x08020008L, 184 | 0x08000200L, 0x00000000L, 0x00020208L, 0x08000200L, 185 | 0x00020008L, 0x08000008L, 0x08000008L, 0x00020000L, 186 | 0x08020208L, 0x00020008L, 0x08020000L, 0x00000208L, 187 | 0x08000000L, 0x00000008L, 0x08020200L, 0x00000200L, 188 | 0x00020200L, 0x08020000L, 0x08020008L, 0x00020208L, 189 | 0x08000208L, 0x00020200L, 0x00020000L, 0x08000208L, 190 | 0x00000008L, 0x08020208L, 0x00000200L, 0x08000000L, 191 | 0x08020200L, 0x08000000L, 0x00020008L, 0x00000208L, 192 | 0x00020000L, 0x08020200L, 0x08000200L, 0x00000000L, 193 | 0x00000200L, 0x00020008L, 0x08020208L, 0x08000200L, 194 | 0x08000008L, 0x00000200L, 0x00000000L, 0x08020008L, 195 | 0x08000208L, 0x00020000L, 0x08000000L, 0x08020208L, 196 | 0x00000008L, 0x00020208L, 0x00020200L, 0x08000008L, 197 | 0x08020000L, 0x08000208L, 0x00000208L, 0x08020000L, 198 | 0x00020208L, 0x00000008L, 0x08020008L, 0x00020200L 199 | ] 200 | 201 | SP4 = [ 202 | 0x00802001L, 0x00002081L, 0x00002081L, 0x00000080L, 203 | 0x00802080L, 0x00800081L, 0x00800001L, 0x00002001L, 204 | 0x00000000L, 0x00802000L, 0x00802000L, 0x00802081L, 205 | 0x00000081L, 0x00000000L, 0x00800080L, 0x00800001L, 206 | 0x00000001L, 0x00002000L, 0x00800000L, 0x00802001L, 207 | 0x00000080L, 0x00800000L, 0x00002001L, 0x00002080L, 208 | 0x00800081L, 0x00000001L, 0x00002080L, 0x00800080L, 209 | 0x00002000L, 0x00802080L, 0x00802081L, 0x00000081L, 210 | 0x00800080L, 0x00800001L, 0x00802000L, 0x00802081L, 211 | 0x00000081L, 0x00000000L, 0x00000000L, 0x00802000L, 212 | 0x00002080L, 0x00800080L, 0x00800081L, 0x00000001L, 213 | 0x00802001L, 0x00002081L, 0x00002081L, 0x00000080L, 214 | 0x00802081L, 0x00000081L, 0x00000001L, 0x00002000L, 215 | 0x00800001L, 0x00002001L, 0x00802080L, 0x00800081L, 216 | 0x00002001L, 0x00002080L, 0x00800000L, 0x00802001L, 217 | 0x00000080L, 0x00800000L, 0x00002000L, 0x00802080L 218 | ] 219 | 220 | SP5 = [ 221 | 0x00000100L, 0x02080100L, 0x02080000L, 0x42000100L, 222 | 0x00080000L, 0x00000100L, 0x40000000L, 0x02080000L, 223 | 0x40080100L, 0x00080000L, 0x02000100L, 0x40080100L, 224 | 0x42000100L, 0x42080000L, 0x00080100L, 0x40000000L, 225 | 0x02000000L, 0x40080000L, 0x40080000L, 0x00000000L, 226 | 0x40000100L, 0x42080100L, 0x42080100L, 0x02000100L, 227 | 0x42080000L, 0x40000100L, 0x00000000L, 0x42000000L, 228 | 0x02080100L, 0x02000000L, 0x42000000L, 0x00080100L, 229 | 0x00080000L, 0x42000100L, 0x00000100L, 0x02000000L, 230 | 0x40000000L, 0x02080000L, 0x42000100L, 0x40080100L, 231 | 0x02000100L, 0x40000000L, 0x42080000L, 0x02080100L, 232 | 0x40080100L, 0x00000100L, 0x02000000L, 0x42080000L, 233 | 0x42080100L, 0x00080100L, 0x42000000L, 0x42080100L, 234 | 0x02080000L, 0x00000000L, 0x40080000L, 0x42000000L, 235 | 0x00080100L, 0x02000100L, 0x40000100L, 0x00080000L, 236 | 0x00000000L, 0x40080000L, 0x02080100L, 0x40000100L 237 | ] 238 | 239 | SP6 = [ 240 | 0x20000010L, 0x20400000L, 0x00004000L, 0x20404010L, 241 | 0x20400000L, 0x00000010L, 0x20404010L, 0x00400000L, 242 | 0x20004000L, 0x00404010L, 0x00400000L, 0x20000010L, 243 | 0x00400010L, 0x20004000L, 0x20000000L, 0x00004010L, 244 | 0x00000000L, 0x00400010L, 0x20004010L, 0x00004000L, 245 | 0x00404000L, 0x20004010L, 0x00000010L, 0x20400010L, 246 | 0x20400010L, 0x00000000L, 0x00404010L, 0x20404000L, 247 | 0x00004010L, 0x00404000L, 0x20404000L, 0x20000000L, 248 | 0x20004000L, 0x00000010L, 0x20400010L, 0x00404000L, 249 | 0x20404010L, 0x00400000L, 0x00004010L, 0x20000010L, 250 | 0x00400000L, 0x20004000L, 0x20000000L, 0x00004010L, 251 | 0x20000010L, 0x20404010L, 0x00404000L, 0x20400000L, 252 | 0x00404010L, 0x20404000L, 0x00000000L, 0x20400010L, 253 | 0x00000010L, 0x00004000L, 0x20400000L, 0x00404010L, 254 | 0x00004000L, 0x00400010L, 0x20004010L, 0x00000000L, 255 | 0x20404000L, 0x20000000L, 0x00400010L, 0x20004010L 256 | ] 257 | 258 | SP7 = [ 259 | 0x00200000L, 0x04200002L, 0x04000802L, 0x00000000L, 260 | 0x00000800L, 0x04000802L, 0x00200802L, 0x04200800L, 261 | 0x04200802L, 0x00200000L, 0x00000000L, 0x04000002L, 262 | 0x00000002L, 0x04000000L, 0x04200002L, 0x00000802L, 263 | 0x04000800L, 0x00200802L, 0x00200002L, 0x04000800L, 264 | 0x04000002L, 0x04200000L, 0x04200800L, 0x00200002L, 265 | 0x04200000L, 0x00000800L, 0x00000802L, 0x04200802L, 266 | 0x00200800L, 0x00000002L, 0x04000000L, 0x00200800L, 267 | 0x04000000L, 0x00200800L, 0x00200000L, 0x04000802L, 268 | 0x04000802L, 0x04200002L, 0x04200002L, 0x00000002L, 269 | 0x00200002L, 0x04000000L, 0x04000800L, 0x00200000L, 270 | 0x04200800L, 0x00000802L, 0x00200802L, 0x04200800L, 271 | 0x00000802L, 0x04000002L, 0x04200802L, 0x04200000L, 272 | 0x00200800L, 0x00000000L, 0x00000002L, 0x04200802L, 273 | 0x00000000L, 0x00200802L, 0x04200000L, 0x00000800L, 274 | 0x04000002L, 0x04000800L, 0x00000800L, 0x00200002L 275 | ] 276 | 277 | SP8 = [ 278 | 0x10001040L, 0x00001000L, 0x00040000L, 0x10041040L, 279 | 0x10000000L, 0x10001040L, 0x00000040L, 0x10000000L, 280 | 0x00040040L, 0x10040000L, 0x10041040L, 0x00041000L, 281 | 0x10041000L, 0x00041040L, 0x00001000L, 0x00000040L, 282 | 0x10040000L, 0x10000040L, 0x10001000L, 0x00001040L, 283 | 0x00041000L, 0x00040040L, 0x10040040L, 0x10041000L, 284 | 0x00001040L, 0x00000000L, 0x00000000L, 0x10040040L, 285 | 0x10000040L, 0x10001000L, 0x00041040L, 0x00040000L, 286 | 0x00041040L, 0x00040000L, 0x10041000L, 0x00001000L, 287 | 0x00000040L, 0x10040040L, 0x00001000L, 0x00041040L, 288 | 0x10001000L, 0x00000040L, 0x10000040L, 0x10040000L, 289 | 0x10040040L, 0x10000000L, 0x00040000L, 0x10001040L, 290 | 0x00000000L, 0x10041040L, 0x00040040L, 0x10000040L, 291 | 0x10040000L, 0x10001000L, 0x10001040L, 0x00000000L, 292 | 0x10041040L, 0x00041000L, 0x00041000L, 0x00001040L, 293 | 0x00001040L, 0x00040040L, 0x10000000L, 0x10041000L 294 | ] 295 | 296 | def desfunc(block, keys): 297 | (leftt, right) = unpack('>II', block) 298 | 299 | work = ((leftt >> 4) ^ right) & 0x0f0f0f0fL 300 | right ^= work 301 | leftt ^= (work << 4) 302 | work = ((leftt >> 16) ^ right) & 0x0000ffffL 303 | right ^= work 304 | leftt ^= (work << 16) 305 | work = ((right >> 2) ^ leftt) & 0x33333333L 306 | leftt ^= work 307 | right ^= (work << 2) 308 | work = ((right >> 8) ^ leftt) & 0x00ff00ffL 309 | leftt ^= work 310 | right ^= (work << 8) 311 | right = ((right << 1) | ((right >> 31) & 1L)) & 0xffffffffL 312 | work = (leftt ^ right) & 0xaaaaaaaaL 313 | leftt ^= work 314 | right ^= work 315 | leftt = ((leftt << 1) | ((leftt >> 31) & 1L)) & 0xffffffffL 316 | 317 | for i in range(0, 32, 4): 318 | work = (right << 28) | (right >> 4) 319 | work ^= keys[i] 320 | fval = SP7[ work & 0x3fL] 321 | fval |= SP5[(work >> 8) & 0x3fL] 322 | fval |= SP3[(work >> 16) & 0x3fL] 323 | fval |= SP1[(work >> 24) & 0x3fL] 324 | work = right ^ keys[i+1] 325 | fval |= SP8[ work & 0x3fL] 326 | fval |= SP6[(work >> 8) & 0x3fL] 327 | fval |= SP4[(work >> 16) & 0x3fL] 328 | fval |= SP2[(work >> 24) & 0x3fL] 329 | leftt ^= fval 330 | work = (leftt << 28) | (leftt >> 4) 331 | work ^= keys[i+2] 332 | fval = SP7[ work & 0x3fL] 333 | fval |= SP5[(work >> 8) & 0x3fL] 334 | fval |= SP3[(work >> 16) & 0x3fL] 335 | fval |= SP1[(work >> 24) & 0x3fL] 336 | work = leftt ^ keys[i+3] 337 | fval |= SP8[ work & 0x3fL] 338 | fval |= SP6[(work >> 8) & 0x3fL] 339 | fval |= SP4[(work >> 16) & 0x3fL] 340 | fval |= SP2[(work >> 24) & 0x3fL] 341 | right ^= fval 342 | 343 | right = (right << 31) | (right >> 1) 344 | work = (leftt ^ right) & 0xaaaaaaaaL 345 | leftt ^= work 346 | right ^= work 347 | leftt = (leftt << 31) | (leftt >> 1) 348 | work = ((leftt >> 8) ^ right) & 0x00ff00ffL 349 | right ^= work 350 | leftt ^= (work << 8) 351 | work = ((leftt >> 2) ^ right) & 0x33333333L 352 | right ^= work 353 | leftt ^= (work << 2) 354 | work = ((right >> 16) ^ leftt) & 0x0000ffffL 355 | leftt ^= work 356 | right ^= (work << 16) 357 | work = ((right >> 4) ^ leftt) & 0x0f0f0f0fL 358 | leftt ^= work 359 | right ^= (work << 4) 360 | 361 | leftt &= 0xffffffffL 362 | right &= 0xffffffffL 363 | return pack('>II', right, leftt) 364 | 365 | 366 | # from vncviewer/rfb/vncauth.c: 367 | fixedkey = [ 23,82,107,6,35,78,88,7 ] 368 | def decrypt_passwd(data): 369 | dk = deskey(pack('8B', *fixedkey), True) 370 | return desfunc(data, dk) 371 | 372 | def generate_response(passwd, challange): 373 | ek = deskey((passwd+'\x00'*8)[:8], False) 374 | return desfunc(challange[:8], ek) + desfunc(challange[8:], ek) 375 | 376 | 377 | # test 378 | if __name__ == '__main__': 379 | key = 'test1234' 380 | plain = 'hello321' 381 | cipher = '\xb4f\x01UnZ1\t' 382 | ek = deskey(key, False) 383 | dk = deskey(key, True) 384 | assert desfunc(plain, ek) == cipher 385 | assert desfunc(desfunc(plain, ek), dk) == plain 386 | assert desfunc(desfunc(plain, dk), ek) == plain 387 | print 'test succeeded.' 388 | -------------------------------------------------------------------------------- /pyvnc2swf/edit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ## 3 | ## pyvnc2swf - edit.py 4 | ## 5 | ## $Id: edit.py,v 1.6 2008/11/16 02:39:40 euske Exp $ 6 | ## 7 | ## Copyright (C) 2005 by Yusuke Shinyama (yusuke at cs . nyu . edu) 8 | ## All Rights Reserved. 9 | ## 10 | ## This is free software; you can redistribute it and/or modify 11 | ## it under the terms of the GNU General Public License as published by 12 | ## the Free Software Foundation; either version 2 of the License, or 13 | ## (at your option) any later version. 14 | ## 15 | ## This software is distributed in the hope that it will be useful, 16 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | ## GNU General Public License for more details. 19 | ## 20 | ## You should have received a copy of the GNU General Public License 21 | ## along with this software; if not, write to the Free Software 22 | ## Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 23 | ## USA. 24 | ## 25 | 26 | import sys, re 27 | from movie import SWFInfo, MovieContainer 28 | from output import FLVVideoStream, MPEGVideoStream, SWFVideoStream, \ 29 | SWFShapeStream, ImageSequenceStream, MovieBuilder 30 | stderr = sys.stderr 31 | 32 | 33 | # range2list: converts strings like "1,5-8" to [1,5,6,7,8]. 34 | class RangeError(ValueError): pass 35 | def range2list(s, n0, n1, step=1): 36 | PAT_RANGE = re.compile(r'^([0-9]*)-([0-9]*)$') 37 | r = [] 38 | for i in s.split(','): 39 | i = i.strip() 40 | if not i: continue 41 | if i.isdigit(): 42 | n = int(i) 43 | if n0 <= n and n <= n1: 44 | r.append(n) 45 | else: 46 | raise RangeError('%d: must be in %d...%d' % (n,n0,n1)) 47 | else: 48 | m = PAT_RANGE.match(i.strip()) 49 | if not m: 50 | raise RangeError('%r: illegal number' % i) 51 | b = n0 52 | if m.group(1): 53 | b = int(m.group(1)) 54 | e = n1 55 | if m.group(2): 56 | e = int(m.group(2)) 57 | if e < b: 58 | (b,e) = (e,b) 59 | if b < n0: 60 | raise RangeError('%d: must be in %d...%d' % (b,n0,n1)) 61 | if n1 < e: 62 | raise RangeError('%d: must be in %d...%d' % (e,n0,n1)) 63 | r.extend(xrange(b,e+1,step)) 64 | return r 65 | 66 | 67 | # reorganize 68 | def reorganize(info, stream, moviefiles, range_str='-', 69 | loop=True, seekbar=True, 70 | step=1, kfinterval=0, 71 | mp3seek=True, mp3skip=0, 72 | debug=0): 73 | movie = MovieContainer(info) 74 | for fname in moviefiles: 75 | if fname.endswith('.swf'): 76 | # vnc2swf file 77 | movie.parse_vnc2swf(fname, True, debug=debug) 78 | elif fname.endswith('.flv'): 79 | # flv file 80 | movie.parse_flv(fname, True, debug=debug) 81 | elif fname.endswith('.vnc'): 82 | # vncrec file 83 | movie.parse_vncrec(fname, debug=debug) 84 | else: 85 | raise ValueError('unsupported format: %r' % fname) 86 | r = range2list(range_str, 0, movie.nframes-1, step) 87 | if movie.info.mp3: 88 | if isinstance(mp3skip, float): 89 | mp3skip = int(mp3skip * movie.info.mp3.sample_rate) 90 | movie.info.mp3.set_initial_skip(mp3skip) 91 | builder = MovieBuilder(movie, stream, mp3seek=mp3seek, kfinterval=kfinterval, debug=debug) 92 | builder.build(r) 93 | stream.close() 94 | movie.info.write_html(seekbar=seekbar, loop=loop) 95 | return 0 96 | 97 | 98 | # main 99 | def main(argv): 100 | import getopt 101 | def usage(): 102 | print >>stderr, '''usage: %s 103 | [-d] [-c] [-t type] [-f|-F frames] [-a mp3file] [-r framerate] 104 | [-S mp3sampleskip] [-C WxH+X+Y] [-B blocksize] [-K keyframe] 105 | [-R framestep] [-s scaling] 106 | -o outfile.swf file1 file2 ... 107 | 108 | Specify one output filename from the following: 109 | *.swf: generate a SWF movie. 110 | *.flv: generate a FLV movie. 111 | *.mpg: generate a MPEG movie. 112 | *.png|*.bmp: save snapshots of given frames as "X-nnn.png" 113 | 114 | -d: debug mode. 115 | -c: compression. 116 | -t {swf5,swf7,flv,mpeg,png,bmp}: specify the output movie type. 117 | -f(-F) frames: frames to extract. e.g. 1-2,100-300,310,500- 118 | -F disables seeking audio. 119 | -R framestep: frame resampling step (default: 1) 120 | -s scaling: scale factor (default: 1.0) 121 | -a filename: attach MP3 file(s). (multiple files can be specified) 122 | -r framerate: override framerate. 123 | -B blocksize: (SWF7 and FLV mode only) blocksize of video packet (must be a multiple of 16) 124 | -K keyframe: keyframe interval 125 | -S N[s]: skip the first N samples (or N seconds) of the sound when the movie starts. 126 | -C WxH+X+Y: crop a specific area of the movie. 127 | -b: disable seekbar. 128 | -l: disable loop. 129 | -z: make the movie scalable. 130 | ''' % argv[0] 131 | return 100 132 | try: 133 | (opts, args) = getopt.getopt(argv[1:], 'dr:o:t:cHa:S:C:B:K:f:F:R:s:blz') 134 | except getopt.GetoptError: 135 | return usage() 136 | # 137 | debug = 0 138 | info = SWFInfo() 139 | range_str = '-' 140 | step = 1 141 | streamtype = None 142 | kfinterval = 0 143 | mp3skip = 0 144 | mp3seek = True 145 | loop = True 146 | seekbar = True 147 | for (k, v) in opts: 148 | if k == '-d': 149 | debug += 1 150 | elif k == '-r': 151 | info.set_framerate(float(v)) 152 | elif k == '-o': 153 | info.filename = v 154 | elif k == '-t': 155 | v = v.lower() 156 | if v not in ('swf5','swf7','mpeg','mpg','flv','png','bmp','gif'): 157 | print >>stderr, 'Invalid output type:', v 158 | return usage() 159 | streamtype = v 160 | elif k == '-a': 161 | fp = file(v, 'rb') 162 | print >>stderr, 'Reading mp3 file: %s...' % v 163 | info.reg_mp3blocks(fp) 164 | fp.close() 165 | elif k == '-S': 166 | if v.endswith('s'): 167 | mp3skip = float(v[:-1]) 168 | else: 169 | mp3skip = int(v) 170 | elif k == '-C': 171 | try: 172 | info.set_clipping(v) 173 | except ValueError: 174 | print >>stderr, 'Invalid clipping specification:', v 175 | return usage() 176 | elif k == '-B': 177 | blocksize = int(v) 178 | assert 0 < blocksize and blocksize <= 256 and blocksize % 16 == 0, 'Invalid block size.' 179 | info.blocksize = blocksize 180 | elif k == '-K': 181 | kfinterval = int(v) 182 | elif k == '-c': 183 | info.compression = True 184 | elif k == '-f': 185 | range_str = v 186 | elif k == '-F': 187 | range_str = v 188 | mp3seek = False 189 | elif k == '-R': 190 | step = int(v) 191 | mp3seek = False 192 | elif k == '-s': 193 | info.scaling = float(v) 194 | assert 0 < info.scaling and info.scaling <= 1.0, 'Invalid scaling.' 195 | elif k == '-b': 196 | seekbar = False 197 | elif k == '-l': 198 | loop = False 199 | elif k == '-z': 200 | info.set_scalable(True) 201 | if not args: 202 | print >>stderr, 'Specify at least one input movie.' 203 | return usage() 204 | if not info.filename: 205 | print >>stderr, 'Specify exactly one output file.' 206 | return usage() 207 | if not streamtype: 208 | v = info.filename 209 | if v.endswith('.swf'): 210 | streamtype = 'swf5' 211 | elif v.endswith('.png'): 212 | streamtype = 'png' 213 | elif v.endswith('.bmp'): 214 | streamtype = 'bmp' 215 | elif v.endswith('.gif'): 216 | streamtype = 'gif' 217 | elif v.endswith('.mpg') or v.endswith('.mpeg'): 218 | streamtype = 'mpeg' 219 | elif v.endswith('.flv'): 220 | streamtype = 'flv' 221 | else: 222 | print >>stderr, 'Unknown stream type.' 223 | return 100 224 | if streamtype == 'mpeg' and not MPEGVideoStream: 225 | print >>stderr, 'MPEGVideoStream is not supported.' 226 | return 100 227 | stream = None 228 | if streamtype == 'swf5': 229 | stream = SWFShapeStream(info, debug=debug) 230 | elif streamtype == 'swf7': 231 | stream = SWFVideoStream(info, debug=debug) 232 | elif streamtype in ('mpg', 'mpeg'): 233 | stream = MPEGVideoStream(info, debug=debug) 234 | elif streamtype == 'flv': 235 | stream = FLVVideoStream(info, debug=debug) 236 | else: 237 | stream = ImageSequenceStream(info, debug=debug) 238 | try: 239 | return reorganize(info, stream, args, range_str, 240 | loop=loop, seekbar=seekbar, 241 | step=step, kfinterval=kfinterval, 242 | mp3seek=mp3seek, mp3skip=mp3skip, 243 | debug=debug) 244 | except RangeError, e: 245 | print >>stderr, 'RangeError:', e 246 | return 100 247 | 248 | if __name__ == "__main__": sys.exit(main(sys.argv)) 249 | -------------------------------------------------------------------------------- /pyvnc2swf/html_templates.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ## 3 | ## pyvnc2swf - seekbar.py 4 | ## 5 | ## $Id: html_templates.py,v 1.2 2008/11/15 10:07:09 euske Exp $ 6 | ## 7 | ## Copyright (C) 2005 by Yusuke Shinyama (yusuke at cs . nyu . edu) 8 | ## All Rights Reserved. 9 | ## 10 | ## This is free software; you can redistribute it and/or modify 11 | ## it under the terms of the GNU General Public License as published by 12 | ## the Free Software Foundation; either version 2 of the License, or 13 | ## (at your option) any later version. 14 | ## 15 | ## This software is distributed in the hope that it will be useful, 16 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | ## GNU General Public License for more details. 19 | ## 20 | ## You should have received a copy of the GNU General Public License 21 | ## along with this software; if not, write to the Free Software 22 | ## Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 23 | ## USA. 24 | ## 25 | 26 | import sys, os, os.path 27 | from swf import SWFParser 28 | 29 | PYVNC2SWF_VERSION = '0.9.2' 30 | 31 | 32 | ## Acknowledgement: 33 | ## 34 | ## The following javascript code is contributed by Jesse Ruderman 35 | ## (http://www.squarefree.com/). He kindly gave us permission to 36 | ## release his code under GPL. Thank you, Jesse! 37 | ## 38 | 39 | # seekbar header 40 | SEEKBAR_HEADER = """ 41 | 242 | 243 | 244 | """ 245 | 246 | # for emacs coloring: " 247 | 248 | 249 | # normal header 250 | NORMAL_HEADER = "\n" 251 | 252 | 253 | # generate_html 254 | def generate_html(out, fname, seekbar=True, loop=True): 255 | parser = SWFParser() 256 | parser.open(fname, header_only=True) 257 | (x,width, y,height) = parser.rect 258 | basename = os.path.basename(fname) 259 | (title, ext) = os.path.splitext(basename) 260 | out.write('\n' 261 | '\n\n%s' % title) 262 | if seekbar: 263 | out.write(SEEKBAR_HEADER) 264 | else: 265 | out.write(NORMAL_HEADER) 266 | dic = { 'title':title, 'width':int(width/20), 'height':int(height/20), 'basename':basename, 267 | 'swf_version':parser.swf_version, 'loop':loop, 'pyvnc2swf_version': PYVNC2SWF_VERSION } 268 | out.write('

%(title)s

\n' 269 | '
\n' 270 | '\n' 272 | ' \n' 273 | ' \n' 274 | ' \n' 275 | ' \n' 276 | '\n' 279 | '
\n' 280 | '
\n' 281 | '
\n' 282 | 'Generated by pyvnc2swf-%(pyvnc2swf_version)s\n' 283 | '
\n' 284 | % dic) 285 | return 286 | 287 | # test 288 | if __name__ == '__main__': 289 | import getopt 290 | def usage(): 291 | print 'usage: %s [-S)eekbarless] [-L)oopless] file' % sys.argv[0] 292 | sys.exit(2) 293 | try: 294 | (opts, args) = getopt.getopt(sys.argv[1:], 'SL') 295 | except getopt.GetoptError: 296 | usage() 297 | (seekbar, loop) = (True, True) 298 | for (k, v) in opts: 299 | if k == '-S': seekbar = False 300 | elif k == '-L': loop = False 301 | if not args: usage() 302 | generate_html(sys.stdout, args[0], seekbar=seekbar, loop=loop) 303 | -------------------------------------------------------------------------------- /pyvnc2swf/image.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ## 3 | ## pyvnc2swf - image.py 4 | ## 5 | ## $Id: image.py,v 1.2 2008/11/15 12:05:08 euske Exp $ 6 | ## 7 | ## Copyright (C) 2005 by Yusuke Shinyama (yusuke at cs . nyu . edu) 8 | ## All Rights Reserved. 9 | ## 10 | ## This is free software; you can redistribute it and/or modify 11 | ## it under the terms of the GNU General Public License as published by 12 | ## the Free Software Foundation; either version 2 of the License, or 13 | ## (at your option) any later version. 14 | ## 15 | ## This software is distributed in the hope that it will be useful, 16 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | ## GNU General Public License for more details. 19 | ## 20 | ## You should have received a copy of the GNU General Public License 21 | ## along with this software; if not, write to the Free Software 22 | ## Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 23 | ## USA. 24 | ## 25 | 26 | import sys 27 | lowerbound = max 28 | upperbound = min 29 | 30 | # format: 1: solid, 31 | # 2: raw (uncompressed) 32 | # 3: DefineBitLossless 33 | # 4: SCREENVIDEOPACKET 34 | IMG_SOLID = 1 35 | IMG_RAW = 2 36 | IMG_LOSSLESS = 3 37 | IMG_VIDEOPACKET = 4 38 | 39 | def bgr2rgb(data): 40 | return ''.join([ data[i+2]+data[i+1]+data[i] for i in xrange(0, len(data), 3) ]) 41 | 42 | try: 43 | # try to pygame 1.6 or newer. 44 | import pygame 45 | print >>sys.stderr, 'Using pygame', pygame.ver 46 | pygame.init() 47 | try: 48 | pygame.mixer.quit() 49 | except NotImplementedError: 50 | pass 51 | def imgsize(img): 52 | return img.get_size() 53 | def create_image(w, h): 54 | return pygame.Surface((w, h), 0, 32) 55 | def create_image_from_string_rgb(w, h, data): 56 | return pygame.image.fromstring(data, (w, h), 'RGB') 57 | def create_image_from_string_rgbx(w, h, data): 58 | return pygame.image.fromstring(data, (w, h), 'RGBX') 59 | def create_image_from_string_xrgb(w, h, data): 60 | return pygame.image.fromstring(data[1:]+'x', (w, h), 'RGBX') 61 | def create_image_from_string_argb(w, h, data): 62 | data = ''.join([ data[i+1]+data[i+2]+data[i+3]+data[i] for i in xrange(0, len(data), 4) ]) 63 | return pygame.image.fromstring(data, (w, h), 'RGBA') 64 | def create_image_from_string_rgb_flipped(w, h, data): 65 | return pygame.image.fromstring(data, (w, h), 'RGB', 1) 66 | def crop_image(img, (x,y,w,h)): 67 | (wm,hm) = img.get_size() 68 | return img.subsurface((x,y,upperbound(wm-x,w),upperbound(hm-y,h))) 69 | def paste_image(dest, src, (x0, y0)): 70 | return dest.blit(src, (x0, y0)) 71 | def save_image(img, fname): 72 | if not fname.endswith('.bmp'): 73 | print >>sys.stderr, 'Warning: this format not supported by pygame, raw rgb is used instead.' 74 | return pygame.image.save(img, fname) 75 | def convert_image_to_string_rgb_flipped(img): 76 | return pygame.image.tostring(img, 'RGB', 1) 77 | def convert_image_to_string_rgb(img): 78 | return pygame.image.tostring(img, 'RGB') 79 | def convert_image_to_string_xrgb(img): 80 | return pygame.image.tostring(img, 'ARGB') 81 | def solid_fill(dest, rect, color): 82 | return dest.fill(color, rect) 83 | def scale_image(img, scaling): 84 | # this might cause segmentation faults sometimes :( 85 | # In that case, use the following instead: 86 | # (w,h) = img.get_size() 87 | # return pygame.transform.scale(img, (int(w*scaling), int(h*scaling))) 88 | return pygame.transform.rotozoom(img, 0, scaling) 89 | 90 | except ImportError: 91 | # use PIL instead 92 | pygame = None 93 | try: 94 | import Image 95 | except ImportError: 96 | print >>sys.stderr, 'Either Pygame or Python Imaging Library is required.' 97 | sys.exit(1) 98 | print >>sys.stderr, 'Using PIL', Image.VERSION 99 | def imgsize(img): 100 | return img.size 101 | def create_image(w, h): 102 | return Image.new('RGB', (w, h)) 103 | def create_image_from_string_rgb(w, h, data): 104 | return Image.fromstring('RGB', (w, h), data, 'raw', 'RGB') 105 | def create_image_from_string_rgbx(w, h, data): 106 | return Image.fromstring('RGB', (w, h), data, 'raw', 'RGBX') 107 | def create_image_from_string_xrgb(w, h, data): 108 | return Image.fromstring('RGB', (w, h), data[1:]+'x', 'raw', 'RGBX') 109 | def create_image_from_string_argb(w, h, data): 110 | return Image.fromstring('RGBA', (w, h), data, 'raw', 'ARGB') 111 | def create_image_from_string_rgb_flipped(w, h, data): 112 | return Image.fromstring('RGB', (w, h), data, 'raw', 'RGB').transpose(Image.FLIP_TOP_BOTTOM) 113 | def crop_image(img, (x0,y0,w,h)): 114 | (wm,hm) = img.size 115 | return img.crop((x0, y0, upperbound(x0+w,wm), upperbound(y0+h,hm))) 116 | def paste_image(dest, src, (x0, y0)): 117 | return dest.paste(src, (x0, y0)) 118 | def save_image(img, fname): 119 | return img.save(fname) 120 | def convert_image_to_string_rgb_flipped(img): 121 | return img.transpose(Image.FLIP_TOP_BOTTOM).tostring('raw', 'RGB') 122 | def convert_image_to_string_rgb(img): 123 | return img.tostring('raw', 'RGB') 124 | def convert_image_to_string_xrgb(img): 125 | return img.tostring('raw', 'XRGB') 126 | def solid_fill(dest, (x0,y0,w,h), color): 127 | return dest.paste(color, (x0, y0, x0+w, y0+h)) 128 | def scale_image(img, scaling): 129 | img = img.copy() 130 | (w,h) = img.size 131 | img.thumbnail((int(w*scaling), int(h*scaling)), resample=1) 132 | return img 133 | 134 | -------------------------------------------------------------------------------- /pyvnc2swf/movie.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ## 3 | ## pyvnc2swf - movie.py 4 | ## 5 | ## $Id: movie.py,v 1.4 2008/11/15 13:42:57 euske Exp $ 6 | ## 7 | ## Copyright (C) 2005 by Yusuke Shinyama (yusuke at cs . nyu . edu) 8 | ## All Rights Reserved. 9 | ## 10 | ## This is free software; you can redistribute it and/or modify 11 | ## it under the terms of the GNU General Public License as published by 12 | ## the Free Software Foundation; either version 2 of the License, or 13 | ## (at your option) any later version. 14 | ## 15 | ## This software is distributed in the hope that it will be useful, 16 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | ## GNU General Public License for more details. 19 | ## 20 | ## You should have received a copy of the GNU General Public License 21 | ## along with this software; if not, write to the Free Software 22 | ## Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 23 | ## USA. 24 | ## 25 | 26 | import sys, zlib, re 27 | from swf import SWFParser, FLVParser, CURSOR_DEPTH 28 | from mp3 import MP3Reader, MP3Storage 29 | from rfb import RFBMovieConverter 30 | from image import IMG_LOSSLESS, IMG_VIDEOPACKET 31 | import html_templates 32 | stderr = sys.stderr 33 | lowerbound = max 34 | upperbound = min 35 | 36 | 37 | ## SWFInfo 38 | ## 39 | class SWFInfo: 40 | 41 | """ 42 | SWFInfo holds information about headers and mp3 data 43 | in a SWF file. The values of this object are changed 44 | as parsing goes on. 45 | """ 46 | 47 | def __init__(self, filename=None): 48 | self.filename = filename 49 | self.compression = None 50 | self.clipping = None 51 | self.framerate = None 52 | self.scaling = None 53 | self.blocksize = None 54 | self.swf_version = None 55 | self.width = None 56 | self.height = None 57 | self.mp3 = None 58 | self.scalable = False 59 | self.author = '' 60 | self.title = '' 61 | self.category = '' 62 | self.tags = '' 63 | self.desc = '' 64 | return 65 | 66 | def __repr__(self): 67 | return '' % \ 68 | (self.filename, self.compression, self.clipping, self.framerate, self.scaling, self.blocksize, self.swf_version, self.mp3) 69 | 70 | def set_defaults(self, w0, h0): # size in pixels 71 | # THIS MUST BE CALLED BEFORE MovieOutputStream.open() 72 | if not self.clipping: 73 | self.clipping = (0,0,w0,h0) 74 | else: 75 | (w0,h0) = (self.clipping[2], self.clipping[3]) 76 | if self.scaling: 77 | (w0,h0) = (int(w0*self.scaling), int(h0*self.scaling)) 78 | if self.width != None and (self.width != w0 or self.height != h0): 79 | print >>stderr, 'Warning: movie size already set: %dx%d' % (self.width, self.height) 80 | elif self.width == None: 81 | (self.width, self.height) = (w0,h0) 82 | print >>stderr, 'Output movie size: %dx%d' % (self.width, self.height) 83 | if not self.framerate: 84 | self.framerate = 12.0 85 | if not self.blocksize: 86 | self.blocksize = 32 87 | return 88 | 89 | def set_scalable(self, scalable): 90 | self.scalable = scalable 91 | return 92 | 93 | def set_framerate(self, framerate): 94 | if self.framerate != None and self.framerate != framerate: 95 | print >>stderr, 'Warning: movie framerate is overridden.' 96 | return 97 | self.framerate = float(framerate) 98 | return 99 | 100 | def set_clipping(self, s): 101 | m = re.match(r'^(\d+)x(\d+)\+(\d+)\+(\d+)$', s) 102 | if not m: 103 | raise ValueError('Invalid clipping spec: %r' % s) 104 | f = map(int, m.groups()) 105 | self.clipping = (f[2],f[3], f[0],f[1]) 106 | return 107 | def get_clipping(self): 108 | if not self.clipping: 109 | raise ValueError('Clipping not set.') 110 | (x,y,w,h) = self.clipping 111 | return '%dx%d+%d+%d' % (w,h,x,y) 112 | 113 | def set_swf_version(self, swf_version): 114 | self.swf_version = swf_version 115 | return 116 | 117 | def set_mp3header(self, isstereo, mp3samplerate, mp3sampleskip): 118 | if not self.mp3: 119 | self.mp3 = MP3Storage() 120 | self.mp3.set_stereo(isstereo) 121 | self.mp3.set_sample_rate(mp3samplerate) 122 | self.mp3.set_initial_skip(mp3sampleskip) 123 | print >>stderr, 'MP3: stereo=%s, samplerate=%d, initialskip=%d' % (isstereo, mp3samplerate, mp3sampleskip) 124 | return 125 | 126 | def reg_mp3blocks(self, fp, length=None, nsamples=None, seeksamples=None): 127 | if not self.mp3: 128 | self.mp3 = MP3Storage() 129 | MP3Reader(self.mp3).read_mp3file(fp, length, nsamples, seeksamples) 130 | return 131 | 132 | def write_html(self, seekbar=True, loop=True, filename=None): 133 | if not (self.swf_version and self.width and self.height): 134 | return 135 | if not filename: 136 | filename = self.filename 137 | if filename.endswith('.swf'): 138 | outfname = filename.replace('.swf','.html') 139 | else: 140 | outfname = filename+'.html' 141 | print >>stderr, 'Writing: %s...' % outfname 142 | out = file(outfname, 'w') 143 | html_templates.generate_html(out, filename, seekbar=seekbar, loop=loop) 144 | out.close() 145 | return 146 | 147 | 148 | ## MovieContainer 149 | ## 150 | class MovieContainer: 151 | 152 | """ 153 | MovieContainer holds all frame images of a movie. 154 | """ 155 | 156 | def __init__(self, info): 157 | self.info = info 158 | self.nframes = 0 159 | self.parsers = [] 160 | return 161 | 162 | # get frame 163 | def get_frame(self, i): 164 | (images, othertags, cursor_info) = ([], [], (None,None)) 165 | for (n,parser) in self.parsers: 166 | if i < n: 167 | (images, othertags, cursor_info) = parser.parse_frame(i) 168 | break 169 | i -= n 170 | return (images, othertags, cursor_info) 171 | 172 | def parse_vnc2swf(self, fname, read_mp3=False, debug=0): 173 | parser = VNC2SWF_Parser(self, read_mp3, debug=debug) 174 | parser.open(fname) 175 | nframes = len(parser.framepos) 176 | self.parsers.append( (nframes, parser) ) 177 | self.nframes += nframes 178 | return self 179 | 180 | def parse_flv(self, fname, read_mp3=False, debug=0): 181 | parser = FLVMovieParser(self, read_mp3, debug=debug) 182 | parser.open(fname) 183 | #raise NotImplementedError 184 | nframes = len(parser.frames) 185 | self.parsers.append( (nframes, parser) ) 186 | self.nframes += nframes 187 | return self 188 | 189 | def parse_vncrec(self, fname, debug=0, outtype='vnc'): 190 | parser = RFBMovieConverter(self, debug=debug, outtype=outtype) 191 | parser.open(fname) 192 | nframes = len(parser.frameinfo) 193 | self.parsers.append( (nframes, parser) ) 194 | self.nframes += nframes 195 | return self 196 | 197 | ## VNC2SWF_Parser 198 | ## 199 | class VNC2SWF_Parser(SWFParser): 200 | 201 | """ 202 | VNC2SWF_Parser parses a SWF file which is specifically 203 | created by vnc2swf. This does not support a generic 204 | Flash file. 205 | """ 206 | 207 | def __init__(self, movie, read_mp3=False, debug=0): 208 | SWFParser.__init__(self, debug) 209 | self.movie = movie 210 | self.read_mp3 = read_mp3 211 | self.video1_cid = None 212 | return 213 | 214 | def parse_header(self): 215 | SWFParser.parse_header(self) 216 | (x,width, y,height) = self.rect 217 | print >>stderr, 'Input movie: version=%d, size=%dx%d, framerate=%dfps, frames=%d, duration=%.1fs.' % \ 218 | (self.swf_version, width/20, height/20, self.framerate, 219 | self.framecount, self.framecount/float(self.framerate)) 220 | self.movie.info.set_framerate(self.framerate) 221 | self.movie.info.set_defaults(width/20, height/20) 222 | return 223 | 224 | def parse_frame(self, i): 225 | self.image1 = {} 226 | self.shape1 = None 227 | self.images = [] 228 | self.othertags = [] 229 | self.cursor_image = None 230 | self.cursor_pos = None 231 | SWFParser.parse_frame(self, i) 232 | return (self.images, self.othertags, (self.cursor_image, self.cursor_pos)) 233 | 234 | def do_tag0(self, tag, length): 235 | return 236 | 237 | def do_unknown_tag(self, tag, length): 238 | data = self.read(length) 239 | self.othertags.append((tag, data)) 240 | return 241 | 242 | def do_tag1(self, tag, length): 243 | # ShowFrame 244 | if self.debug: 245 | print >>stderr, 'ShowFrame' 246 | return 247 | 248 | def do_tag9(self, tag, length): 249 | # SetBackgroundColor 250 | bgcolor = self.readrgb() 251 | if self.debug: 252 | print >>stderr, 'BGColor:', bgcolor 253 | return 254 | 255 | def do_tag20(self, tag, length): 256 | # DefineBitsLossless 257 | cid = self.readui16() 258 | fmt = self.readui8() 259 | width = self.readui16() 260 | height = self.readui16() 261 | length -= 7 262 | tablesize = 0 263 | if fmt == 3: 264 | tablesize = self.readui8()+1 265 | length -= 1 266 | if fmt == 5: # RGB or RGBA 267 | data = self.read(length) 268 | if self.debug: 269 | print >>stderr, 'DefineBitsLossless:', cid, fmt, width, height, len(data) 270 | self.image1[cid] = (width, height, (IMG_LOSSLESS, data)) 271 | return 272 | # DefineBitsLossless2 273 | do_tag36 = do_tag20 274 | 275 | def do_tag32(self, tag, length): 276 | # DefineShape3 277 | sid = self.readui16() 278 | rect = self.readrect() 279 | (fillstyles, linestyles) = self.read_style(3) 280 | shape = self.read_shape(3, fillstyles, linestyles) 281 | if fillstyles: 282 | cid = fillstyles[0][3] 283 | if self.debug: 284 | print >>stderr, 'Shape', sid, cid, rect, shape, fillstyles, linestyles 285 | self.shape1 = (sid, cid) 286 | return 287 | 288 | def do_tag26(self, tag, length): 289 | # PlaceObject2 290 | flags = self.readui8() 291 | depth = self.readui16() 292 | (sid, ratio, name) = (None, None, None) 293 | (scalex,scaley, rot0,rot1, transx,transy) = (None, None, None, None, None, None) 294 | if flags & 2: 295 | sid = self.readui16() 296 | if flags & 4: 297 | (scalex,scaley, rot0,rot1, transx,transy) = self.readmatrix() 298 | #assert not (flags & 8) 299 | if flags & 16: 300 | ratio = self.readui16() 301 | if flags & 32: 302 | name = self.readstring() 303 | #assert not (flags & 64) 304 | #assert not (flags & 128) 305 | if self.debug: 306 | print >>stderr, 'Place', flags, depth, sid, (scalex,scaley, rot0,rot1, transx,transy) 307 | if depth == CURSOR_DEPTH: 308 | # this is a cursor sprite! 309 | if sid: 310 | (sid0,cid) = self.shape1 311 | if sid0 == sid and cid in self.image1: 312 | (width, height, (t, data)) = self.image1[cid] 313 | self.cursor_image = (width, height, 0, 0, zlib.decompress(data)) 314 | if transx != None: 315 | self.cursor_pos = (transx/20, transy/20) 316 | elif not sid or sid == self.video1_cid: 317 | # ignore video frame 318 | pass 319 | elif self.shape1 and transx != None: 320 | (sid0,cid) = self.shape1 321 | if sid0 == sid and cid in self.image1: 322 | data = self.image1[cid] 323 | del self.image1[cid] 324 | self.images.append(((transx/20, transy/20), data)) 325 | self.shape1 = None 326 | return 327 | 328 | def do_tag28(self, tag, length): 329 | # RemoveObject2 330 | depth = self.readui16() 331 | if self.debug: 332 | print >>stderr, 'RemoveObject', depth 333 | return 334 | 335 | def scan_tag60(self, tag, length): 336 | # DefineVideoStream 337 | if self.video1_cid: 338 | print >>stderr, 'DefineVideoStream already appeared.' 339 | return 340 | cid = self.readui16() 341 | frames = self.readui16() 342 | width = self.readui16() 343 | height = self.readui16() 344 | flags = self.readui8() # ignore this. 345 | codec = self.readui8() # must be ScreenVideo 346 | if codec == 3: 347 | self.video1_cid = cid 348 | if self.debug: 349 | print >>stderr, 'DefineVideoStream', cid, frames, width, height, flags, codec 350 | return 351 | def do_tag60(self, tag, length): 352 | return 353 | 354 | def do_tag61(self, tag, length): 355 | # VideoFrame 356 | stream_id = self.readui16() 357 | if self.video1_cid != stream_id: return # Video ID does not match 358 | framenum = self.readui16() 359 | self.setbuff() 360 | (frametype, codecid) = self.readbits(4), self.readbits(4) 361 | if codecid != 3: return # must be ScreenVideo 362 | (blockwidth, imagewidth) = self.readbits(4), self.readbits(12) 363 | (blockheight, imageheight) = self.readbits(4), self.readbits(12) 364 | blockwidth = (blockwidth+1)*16 365 | blockheight = (blockheight+1)*16 366 | if self.debug: 367 | print >>stderr, 'VideoFrame', framenum, frametype, ':', blockwidth, imagewidth, blockheight, imageheight 368 | hblocks = (imagewidth+blockwidth-1)/blockwidth 369 | vblocks = (imageheight+blockheight-1)/blockheight 370 | for y in xrange(0, vblocks): 371 | for x in xrange(0, hblocks): 372 | length = self.readub16() 373 | if length: 374 | data = self.read(length) 375 | x0 = x*blockwidth 376 | y0 = imageheight-(y+1)*blockheight 377 | w = upperbound(blockwidth, imagewidth-x0) 378 | h = blockheight 379 | if y0 < 0: 380 | h += y0 381 | y0 = 0 382 | self.images.append( ((x0,y0), (w,h,(IMG_VIDEOPACKET,data))) ) 383 | return 384 | 385 | def scan_tag18(self, tag, length): 386 | # SoundStreamHead 387 | if not self.read_mp3: return 388 | flags1 = self.readui8() 389 | flags2 = self.readui8() 390 | playrate = (flags1 & 0x0c) >> 2 391 | if not (flags1 & 2): return 392 | # playbacksoundsize is given 393 | playstereo = flags1 & 1 394 | compression = (flags2 & 0xf0) >> 4 395 | if compression != 2: return 396 | # must be mp3 397 | samplerate = (flags2 & 0x0c) >> 2 398 | if samplerate == 0: return 399 | samplerate = [0,11025,22050,44100][samplerate] 400 | if not (flags2 & 2): return 401 | # streamsoundsize is given 402 | streamstereo = flags2 & 1 403 | avgsamplecount = self.readui16() 404 | latseek = self.readui16() 405 | self.movie.info.set_mp3header(streamstereo, samplerate, latseek) 406 | if self.debug: 407 | print >>stderr, 'SoundStreamHeader', flags1, flags2, avgsamplecount, latseek 408 | return 409 | def do_tag18(self, tag, length): 410 | return 411 | 412 | def scan_tag19(self, tag, length): 413 | # SoundStreamBlock 414 | if not self.read_mp3: return 415 | nsamples = self.readui16() 416 | seeksamples = self.readsi16() 417 | self.movie.info.reg_mp3blocks(self.fp, length-4, nsamples, seeksamples) 418 | if self.debug: 419 | print >>stderr, 'SoundStreamBlock', nsamples, seeksamples 420 | return 421 | def do_tag19(self, tag, length): 422 | return 423 | 424 | 425 | ## FLVMovieParser 426 | ## 427 | class FLVMovieParser(FLVParser): 428 | 429 | def __init__(self, movie, read_mp3, debug=0): 430 | FLVParser.__init__(self, debug=debug) 431 | self.movie = movie 432 | self.read_mp3 = read_mp3 433 | self.framerate = 12 434 | return 435 | 436 | def open(self, fname): 437 | FLVParser.open(self, fname) 438 | self.movie.info.set_framerate(self.framerate) 439 | for (tag, _, _, offset) in self.tags: 440 | if tag == 9: 441 | self.fp.seek(offset+1) 442 | self.setbuff() 443 | (_, imagewidth) = self.readbits(4), self.readbits(12) 444 | (_, imageheight) = self.readbits(4), self.readbits(12) 445 | self.movie.info.set_defaults(imagewidth, imageheight) 446 | break 447 | self.frames = [] 448 | tagids = [] 449 | for (tagid,(_,_,t,_)) in enumerate(self.tags): 450 | if len(self.frames)*1000 / self.framerate < t: 451 | self.frames.append(tagids) 452 | tagids = [] 453 | tagids.append(tagid) 454 | self.frames.append(tagids) 455 | return 456 | 457 | def parse_frame(self, i): 458 | self.images = [] 459 | self.othertags = [] 460 | for tagid in self.frames[i]: 461 | self.process_tag(tagid) 462 | return (self.images, self.othertags, (None, None)) 463 | 464 | def process_tag(self, tagid): 465 | (tag, _, _, offset) = self.tags[tagid] 466 | if tag != 9: return # Video 467 | self.fp.seek(offset) 468 | self.setbuff() 469 | (frametype, codecid) = self.readbits(4), self.readbits(4) 470 | if codecid != 3: return # must be ScreenVideo 471 | (blockwidth, imagewidth) = self.readbits(4), self.readbits(12) 472 | (blockheight, imageheight) = self.readbits(4), self.readbits(12) 473 | blockwidth = (blockwidth+1)*16 474 | blockheight = (blockheight+1)*16 475 | if self.debug: 476 | print >>stderr, 'VideoFrame', framenum, frametype, ':', blockwidth, imagewidth, blockheight, imageheight 477 | hblocks = (imagewidth+blockwidth-1)/blockwidth 478 | vblocks = (imageheight+blockheight-1)/blockheight 479 | for y in xrange(0, vblocks): 480 | for x in xrange(0, hblocks): 481 | length = self.readub16() 482 | if length: 483 | data = self.read(length) 484 | x0 = x*blockwidth 485 | y0 = imageheight-(y+1)*blockheight 486 | w = upperbound(blockwidth, imagewidth-x0) 487 | h = blockheight 488 | if y0 < 0: 489 | h += y0 490 | y0 = 0 491 | self.images.append( ((x0,y0), (w,h,(IMG_VIDEOPACKET,data))) ) 492 | return 493 | 494 | 495 | # main 496 | if __name__ == '__main__': 497 | info = SWFInfo() 498 | movie = MovieContainer(info).parse_vnc2swf(sys.argv[1], read_mp3=True, debug=1) 499 | print movie.nframes, info 500 | -------------------------------------------------------------------------------- /pyvnc2swf/mp3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ## 3 | ## pyvnc2swf - mp3.py 4 | ## 5 | ## $Id: mp3.py,v 1.2 2008/07/12 06:06:34 euske Exp $ 6 | ## 7 | ## Copyright (C) 2005 by Yusuke Shinyama (yusuke at cs . nyu . edu) 8 | ## All Rights Reserved. 9 | ## 10 | ## This is free software; you can redistribute it and/or modify 11 | ## it under the terms of the GNU General Public License as published by 12 | ## the Free Software Foundation; either version 2 of the License, or 13 | ## (at your option) any later version. 14 | ## 15 | ## This software is distributed in the hope that it will be useful, 16 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | ## GNU General Public License for more details. 19 | ## 20 | ## You should have received a copy of the GNU General Public License 21 | ## along with this software; if not, write to the Free Software 22 | ## Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 23 | ## USA. 24 | ## 25 | 26 | import sys 27 | from struct import pack, unpack 28 | stderr = sys.stderr 29 | 30 | 31 | ## MP3Storage 32 | ## 33 | class MP3Storage: 34 | 35 | def __init__(self, debug=0): 36 | self.debug = debug 37 | self.isstereo = None 38 | self.bit_rate = None 39 | self.sample_rate = None 40 | self.initial_skip = 0 41 | self.frames = [] 42 | # 43 | self.played_samples = 0 44 | self.playing_frame = 0 45 | self.seeksamples = 0 46 | return 47 | 48 | def __repr__(self): 49 | return '' % \ 50 | (self.isstereo, self.bit_rate, self.sample_rate, self.initial_skip, len(self.frames)) 51 | 52 | def set_stereo(self, isstereo): 53 | if self.isstereo == None: 54 | self.isstereo = isstereo 55 | elif self.isstereo != isstereo: 56 | print >>stderr, 'mp3: isstereo does not match!' 57 | return 58 | 59 | def set_bit_rate(self, bit_rate): 60 | if self.bit_rate == None: 61 | self.bit_rate = bit_rate 62 | elif self.bit_rate != bit_rate: 63 | print >>stderr, 'mp3: bit_rate does not match! (variable bitrate mp3 cannot be used for SWF)' 64 | return 65 | 66 | def set_sample_rate(self, sample_rate): 67 | if self.sample_rate == None: 68 | self.sample_rate = sample_rate 69 | elif self.sample_rate != sample_rate: 70 | print >>stderr, 'mp3: sample_rate does not match! (variable bitrate mp3 cannot be used for SWF)' 71 | return 72 | 73 | def set_initial_skip(self, initial_skip): 74 | if initial_skip: 75 | self.initial_skip = initial_skip 76 | return 77 | 78 | def add_frame(self, nsamples, frame): 79 | self.frames.append((nsamples, frame)) 80 | return 81 | 82 | def needsamples(self, t): 83 | return int(self.sample_rate * t) + self.initial_skip 84 | 85 | def get_frames_until(self, t): 86 | # write mp3 frames 87 | # 88 | # Before: 89 | # 90 | # MP3 |----|played_samples 91 | # SWF |-------|-----|needsamples(t) 92 | # prev cur. 93 | # 94 | # After: 95 | # ->| |<- next seeksamples 96 | # MP3 |----------|played_samples 97 | # SWF |-------|-----|needsamples(t) 98 | # prev cur. 99 | needsamples = self.needsamples(t) 100 | if needsamples < 0: 101 | return (0, 0, []) 102 | nsamples = 0 103 | frames = [] 104 | while self.playing_frame < len(self.frames): 105 | (samples,data) = self.frames[self.playing_frame] 106 | if needsamples <= self.played_samples+nsamples+samples: break 107 | nsamples += samples 108 | frames.append(data) 109 | self.playing_frame += 1 110 | seeksamples = self.seeksamples 111 | self.played_samples += nsamples 112 | self.seeksamples = needsamples-self.played_samples # next seeksample 113 | return (nsamples, seeksamples, frames) 114 | 115 | def seek_frame(self, t): 116 | needsamples = self.needsamples(t) 117 | self.played_samples = 0 118 | for (i,(samples,data)) in enumerate(self.frames): 119 | if needsamples <= self.played_samples+samples: break 120 | self.played_samples += samples 121 | self.playing_frame = i 122 | self.seeksamples = needsamples-self.played_samples 123 | return 124 | 125 | 126 | ## MP3Reader 127 | ## 128 | class MP3Reader: 129 | 130 | """ 131 | read MPEG frames. 132 | """ 133 | 134 | def __init__(self, storage): 135 | self.storage = storage 136 | return 137 | 138 | def read(self, n): 139 | if self.length != None: 140 | if self.length <= 0: 141 | return '' 142 | self.length -= n 143 | return self.fp.read(n) 144 | 145 | BIT_RATE = { 146 | (1,1): (0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0), 147 | (1,2): (0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0), 148 | (1,3): (0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0), 149 | (2,1): (0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 144, 176, 192, 224, 256, 0), 150 | (2,2): (0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0), 151 | (2,3): (0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0), 152 | } 153 | SAMPLE_RATE = { 154 | 3: (44100, 48000, 32000), # V1 155 | 2: (22050, 24000, 16000), # V2 156 | 0: (11025, 12000, 8000), # V2.5 157 | } 158 | def read_mp3file(self, fp, length=None, totalsamples0=None, seeksamples=None, verbose=False): 159 | """parameter seeksamples is ignored.""" 160 | self.fp = fp 161 | self.length = length 162 | totalsamples = 0 163 | while 1: 164 | x = self.read(4) 165 | if len(x) < 4: break 166 | if x.startswith('TAG'): 167 | # TAG - ignored 168 | data = x[3]+self.read(128-4) 169 | if verbose: 170 | print >>stderr, 'TAG', repr(data) 171 | continue 172 | elif x.startswith('ID3'): 173 | # ID3 - ignored 174 | id3version = x[3]+fp.read(1) 175 | flags = ord(fp.read(1)) 176 | s = [ ord(c) & 0x7f for c in fp.read(4) ] 177 | size = (s[0]<<21) | (s[1]<<14) | (s[2]<<7) | s[3] 178 | data = fp.read(size) 179 | if verbose: 180 | print >>stderr, 'ID3', repr(data) 181 | continue 182 | h = unpack('>L', x)[0] 183 | #if (h & 0xfffb0003L) != 0xfffb0000L: continue 184 | # All sync bits (b31-21) are set? 185 | if (h & 0xffe00000L) != 0xffe00000L: continue 186 | # MPEG Audio Version ID (0, 2 or 3) 187 | version = (h & 0x00180000L) >> 19 188 | if version == 1: continue 189 | # Layer (3: mp3) 190 | layer = 4 - ((h & 0x00060000L) >> 17) 191 | if layer == 4: continue 192 | # Protection 193 | protected = not (h & 0x00010000L) 194 | # Bitrate 195 | b = (h & 0xf000) >> 12 196 | if b == 0 or b == 15: continue 197 | # Frequency 198 | s = (h & 0x0c00) >> 10 199 | if s == 3: continue 200 | if version == 3: # V1 201 | bit_rate = self.BIT_RATE[(1,layer)][b] 202 | else: # V2 or V2.5 203 | bit_rate = self.BIT_RATE[(2,layer)][b] 204 | self.storage.set_bit_rate(bit_rate) 205 | sample_rate = self.SAMPLE_RATE[version][s] 206 | self.storage.set_sample_rate(sample_rate) 207 | #print (version, layer, bit_rate, sample_rate) 208 | nsamples = 1152 209 | if sample_rate <= 24000: 210 | nsamples = 576 211 | pad = (h & 0x0200) >> 9 212 | channel = (h & 0xc0) >> 6 213 | self.storage.set_stereo(1-(channel/2)) 214 | joint = (h & 0x30) >> 4 215 | copyright = bool(h & 8) 216 | original = bool(h & 4) 217 | emphasis = h & 3 218 | if version == 3: 219 | framesize = 144000 * bit_rate / sample_rate + pad 220 | else: 221 | framesize = 72000 * bit_rate / sample_rate + pad 222 | if protected: 223 | # skip 16bit CRC 224 | self.read(2) 225 | if verbose: 226 | print >>stderr, 'Frame: bit_rate=%dk, sample_rate=%d, framesize=%d' % \ 227 | (bit_rate, sample_rate, framesize) 228 | data = x+self.read(framesize-4) 229 | self.storage.add_frame(nsamples, data) 230 | totalsamples += nsamples 231 | if totalsamples0: 232 | assert totalsamples == totalsamples0 233 | return 234 | 235 | 236 | if __name__ == "__main__": 237 | s = MP3Storage(True) 238 | MP3Reader(s).read_mp3file(file(sys.argv[1]), verbose=1) 239 | -------------------------------------------------------------------------------- /pyvnc2swf/play.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ## 3 | ## pyvnc2swf - play.py 4 | ## 5 | ## $Id: play.py,v 1.6 2008/11/16 02:39:40 euske Exp $ 6 | ## 7 | ## Copyright (C) 2005 by Yusuke Shinyama (yusuke at cs . nyu . edu) 8 | ## All Rights Reserved. 9 | ## 10 | ## This is free software; you can redistribute it and/or modify 11 | ## it under the terms of the GNU General Public License as published by 12 | ## the Free Software Foundation; either version 2 of the License, or 13 | ## (at your option) any later version. 14 | ## 15 | ## This software is distributed in the hope that it will be useful, 16 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | ## GNU General Public License for more details. 19 | ## 20 | ## You should have received a copy of the GNU General Public License 21 | ## along with this software; if not, write to the Free Software 22 | ## Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 23 | ## USA. 24 | ## 25 | 26 | import sys, os.path, subprocess 27 | import pygame 28 | from image import create_image_from_string_argb 29 | from movie import SWFInfo, MovieContainer 30 | from output import SWFScreen, MovieOutputStream, MovieBuilder 31 | lowerbound = max 32 | upperbound = min 33 | stderr = sys.stderr 34 | 35 | ## PygameMoviePlayer 36 | ## 37 | class PygameMoviePlayer(MovieOutputStream): 38 | 39 | """ 40 | A simple movie player using Pygame. 41 | """ 42 | 43 | font_size = 24 44 | 45 | def __init__(self, movie, debug=0): 46 | MovieOutputStream.__init__(self, movie.info, debug) 47 | self.builder = MovieBuilder(movie, self, debug) 48 | self.movie = movie 49 | return 50 | 51 | # MovieOuputStream methods 52 | 53 | def open(self): 54 | MovieOutputStream.open(self) 55 | # open window 56 | (x,y,w,h) = self.info.clipping 57 | self.imagesize = ( int(w*(self.info.scaling or 1)), int(h*(self.info.scaling or 1)) ) 58 | self.screen = SWFScreen(x, y, w, h) 59 | (self.winwidth, self.winheight) = self.imagesize 60 | self.font = pygame.font.SysFont(pygame.font.get_default_font(), self.font_size) 61 | (fw1,fh1) = self.font.size('00000 ') 62 | (fw2,fh2) = self.font.size('[>] ') 63 | self.panel_x0 = 0 64 | self.panel_x1 = fw1 65 | self.panel_x2 = fw1+fw2 66 | self.panel_y0 = self.winheight 67 | self.panel_y1 = self.winheight + fh1/2 68 | self.panel_h = fh1 69 | self.panel_w = lowerbound(64, self.winwidth-fw1-fw2-4) 70 | self.slide_h = fh1/2 71 | self.slide_w = 8 72 | self.actualwidth = self.panel_w+fw1+fw2+4 73 | pygame.display.set_caption(self.info.filename, self.info.filename) 74 | self.window = pygame.display.set_mode((self.actualwidth, self.winheight+self.panel_h)) 75 | self.cursor_image = None 76 | self.cursor_pos = None 77 | self.playing = True 78 | self.mp3_out = self.mp3_dec = None 79 | #import pymedia 80 | #self.mp3_out = subprocess.Popen(['mpg123','-q','-'], stdin=subprocess.PIPE) 81 | #self.mp3_dec = pymedia.audio.acodec.Decoder('mp3') 82 | #self.mp3_out = pymedia.audio.sound.Output(44100,2,pymedia.audio.sound.AFMT_S16_LE) 83 | return 84 | 85 | def paint_frame(self, (images, othertags, cursor_info)): 86 | for ((x0,y0), (w,h,data)) in images: 87 | self.screen.paint_image(x0, y0, w, h, data) 88 | if cursor_info: 89 | (cursor_image, cursor_pos) = cursor_info 90 | if cursor_image: 91 | (w, h, dx, dy, data) = cursor_image 92 | self.cursor_offset = (dx, dy) 93 | self.cursor_image = create_image_from_string_argb(w, h, data) 94 | if cursor_pos: 95 | self.cursor_pos = cursor_pos 96 | return 97 | 98 | def preserve_frame(self): 99 | img = pygame.Surface(self.screen.buf.get_size()) 100 | img.blit(self.screen.buf, (0,0)) 101 | return img 102 | 103 | def recover_frame(self, img): 104 | self.screen.buf.blit(img, (0,0)) 105 | return 106 | 107 | # additional methods 108 | 109 | def show_status(self): 110 | f = self.current_frame 111 | n = self.movie.nframes 112 | s = '%05d' % f 113 | self.window.fill((0,0,0), (0, self.panel_y0, self.actualwidth, self.panel_h)) 114 | self.window.blit(self.font.render(s, 0, (255,255,255)), (0, self.panel_y0)) 115 | if self.playing: 116 | self.window.blit(self.font.render('[>]', 0, (0,255,0)), (self.panel_x1, self.panel_y0)) 117 | else: 118 | self.window.blit(self.font.render('[||]', 0, (255,0,0)), (self.panel_x1, self.panel_y0)) 119 | self.window.fill((255,255,255), (self.panel_x2, self.panel_y1, self.panel_w, 1)) 120 | x = self.panel_x2 + self.panel_w*f/n - self.slide_w/2 121 | y = self.panel_y1 - self.slide_h/2 122 | self.window.fill((255,255,255), (x, y, self.slide_w, self.slide_h)) 123 | return 124 | 125 | def update(self): 126 | surface = self.screen.buf 127 | if self.info.scaling: 128 | # rotozoom is still very unstable... it sometime causes segfault :( 129 | # in case it doesn't work, use scale instead. 130 | # surface = pygame.transform.scale(surface, self.imagesize) 131 | # surface.set_alpha() 132 | surface = pygame.transform.rotozoom(surface, 0, self.info.scaling) 133 | self.window.blit(surface, (0,0)) 134 | if self.cursor_image and self.cursor_pos: 135 | (x, y) = self.cursor_pos 136 | (dx, dy) = self.cursor_offset 137 | self.window.blit(self.cursor_image, (x-dx, y-dy)) 138 | self.show_status() 139 | pygame.display.update() 140 | if self.mp3_out and self.info.mp3: 141 | t = (self.current_frame+1) / self.info.framerate 142 | (nsamples, seeksamples, mp3frames) = self.info.mp3.get_frames_until(t) 143 | r = self.mp3_dec.decode(''.join(mp3frames)) 144 | self.mp3_out.play(r.data) 145 | return 146 | 147 | def toggle_playing(self): 148 | self.playing = not self.playing 149 | if self.playing and self.movie.nframes-1 <= self.current_frame: 150 | self.current_frame = 0 151 | return 152 | 153 | def seek(self, goal): 154 | self.current_frame = upperbound(lowerbound(goal, 0), self.movie.nframes-1) 155 | self.builder.seek(self.current_frame) 156 | self.playing = False 157 | self.update() 158 | return 159 | 160 | def play(self): 161 | drag = False 162 | loop = True 163 | ticks0 = 0 164 | self.current_frame = 0 165 | self.builder.start() 166 | while loop: 167 | if self.playing: 168 | events = pygame.event.get() 169 | else: 170 | events = [pygame.event.wait()] 171 | for e in events: 172 | if e.type in (pygame.MOUSEBUTTONDOWN, pygame.MOUSEMOTION): 173 | (x,y) = e.pos 174 | if (e.type == pygame.MOUSEBUTTONDOWN and y < self.panel_y0): 175 | # the screen clicked 176 | self.toggle_playing() 177 | elif (self.panel_y0 < y and (e.type == pygame.MOUSEBUTTONDOWN or drag)): 178 | # slide bar dragging 179 | drag = True 180 | (x,y) = e.pos 181 | self.seek((x-self.panel_x2)*self.movie.nframes/self.panel_w) 182 | elif e.type == pygame.MOUSEBUTTONUP: 183 | drag = False 184 | elif e.type == pygame.KEYDOWN: 185 | if e.key in (13, 32): # space or enter 186 | self.toggle_playing() 187 | elif e.key in (113, 27): # 'q'uit, esc 188 | loop = False 189 | elif e.key in (115, 83): # 's'napshot 190 | (root, ext) = os.path.splitext(self.info.filename) 191 | fname = '%s-%05d.bmp' % (root, self.current_frame) 192 | pygame.image.save(self.screen.buf, fname) 193 | print >>stderr, 'Save:', fname 194 | elif e.key == 275: # right 195 | self.current_frame += 1 196 | self.seek(self.current_frame) 197 | elif e.key == 276: # left 198 | self.current_frame -= 1 199 | self.seek(self.current_frame) 200 | else: 201 | print >>stderr, 'Unknown key:', e 202 | elif e.type == pygame.QUIT: 203 | # window close attmpt 204 | loop = False 205 | if self.playing: 206 | self.builder.seek(self.current_frame) 207 | if self.movie.nframes-1 <= self.current_frame: 208 | # reach the end. 209 | self.playing = False 210 | else: 211 | self.current_frame += 1 212 | ticks1 = pygame.time.get_ticks() 213 | d = lowerbound(int(1000.0/self.info.framerate), ticks0-ticks1) 214 | ticks0 = ticks1 215 | pygame.time.wait(d) 216 | self.update() 217 | # loop end 218 | self.builder.finish() 219 | self.close() 220 | return 221 | 222 | 223 | # play 224 | def play(moviefiles, info, debug=0): 225 | movie = MovieContainer(info) 226 | for fname in moviefiles: 227 | if fname.endswith('.swf'): 228 | # vnc2swf file 229 | movie.parse_vnc2swf(fname, True, debug=debug) 230 | elif fname.endswith('.flv'): 231 | # flv file 232 | movie.parse_flv(fname, True, debug=debug) 233 | elif fname.endswith('.vnc'): 234 | # vncrec file 235 | movie.parse_vncrec(fname, debug=debug, outtype="vnc") 236 | elif fname.endswith('.novnc'): 237 | # vncrec file 238 | movie.parse_vncrec(fname, debug=debug, outtype="novnc") 239 | else: 240 | raise ValueError('unsupported format: %r' % fname) 241 | info.filename = os.path.basename(fname) 242 | PygameMoviePlayer(movie, debug=debug).play() 243 | return 244 | 245 | # main 246 | def main(argv): 247 | import getopt, re 248 | def usage(): 249 | print 'usage: %s [-d] [-r framerate] [-C WxH+X+Y] [-s scaling] file1 file2 ...' % argv[0] 250 | return 100 251 | try: 252 | (opts, args) = getopt.getopt(argv[1:], 'dr:C:s:') 253 | except getopt.GetoptError: 254 | return usage() 255 | # 256 | debug = 0 257 | info = SWFInfo() 258 | for (k, v) in opts: 259 | if k == '-d': 260 | debug += 1 261 | elif k == '-r': 262 | info.set_framerate(float(v)) 263 | elif k == '-C': 264 | m = re.match(r'^(\d+)x(\d+)\+(\d+)\+(\d+)$', v) 265 | if not m: 266 | print >>stderr, 'Invalid clipping specification:', v 267 | return usage() 268 | x = map(int, m.groups()) 269 | info.clipping = (x[2],x[3], x[0],x[1]) 270 | elif k == '-s': 271 | info.scaling = float(v) 272 | if not args: 273 | print >>stderr, 'Specify at least one input movie.' 274 | return usage() 275 | return play(args, info, debug=debug) 276 | 277 | if __name__ == "__main__": sys.exit(main(sys.argv)) 278 | -------------------------------------------------------------------------------- /pyvnc2swf/record_sound.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # record_sound.py - Sound recording routine with PyMedia. 4 | # Contributed by David Fraser 5 | 6 | import sys, time 7 | import pymedia.audio.sound as sound 8 | import pymedia.audio.acodec as acodec 9 | import pymedia.muxer as muxer 10 | import threading 11 | 12 | class voiceRecorder: 13 | def __init__(self, name ): 14 | self.name = name 15 | self.finished = False 16 | 17 | def record(self): 18 | self.snd.start() 19 | while not self.finished: 20 | s= self.snd.getData() 21 | if s and len( s ): 22 | for fr in self.ac.encode( s ): 23 | # We definitely should use mux first, but for 24 | 25 | # simplicity reasons this way it'll work also 26 | block = self.mux.write( self.stream_index, fr ) 27 | if block is not None: 28 | self.f.write( block ) 29 | else: 30 | time.sleep( .003 ) 31 | # time.sleep( 0 ) 32 | self.snd.stop() 33 | 34 | def run(self): 35 | print "recording to", self.name 36 | self.f= open( self.name, 'wb' ) 37 | # Minimum set of parameters we need to create Encoder 38 | 39 | cparams= { 'id': acodec.getCodecID( 'mp3' ), 40 | 'bitrate': 128000, 41 | 'sample_rate': 44100, 42 | 'channels': 1 } 43 | self.ac= acodec.Encoder( cparams ) 44 | self.snd= sound.Input( 44100, 1, sound.AFMT_S16_LE ) 45 | self.mux = muxer.Muxer("mp3") 46 | self.stream_index = self.mux.addStream( muxer.CODEC_TYPE_AUDIO, self.ac.getParams() ) 47 | block = self.mux.start() 48 | if block: 49 | self.f.write(block) 50 | # Loop until Ctrl-C pressed or finished set from outside 51 | 52 | self.finished = False 53 | thread = threading.Thread(target=self.record) 54 | thread.start() 55 | try: 56 | while not self.finished: 57 | time.sleep( .003 ) 58 | except KeyboardInterrupt: 59 | self.finished = True 60 | print "finishing recording to", self.name 61 | # Stop listening the incoming sound from the microphone or line in 62 | thread.join() 63 | footer = self.mux.end() 64 | if footer is not None: 65 | self.f.write(footer) 66 | self.f.close() 67 | print "finished recording to", self.name 68 | print "snipping leading zeroes..." 69 | f = open( self.name, "rb" ) 70 | buffer = f.read() 71 | f.close() 72 | buffer = buffer.lstrip(chr(0)) 73 | f = open( self.name, "wb" ) 74 | f.write( buffer ) 75 | f.close() 76 | print "snipped leading zeroes" 77 | 78 | # ---------------------------------------------------------------------------------- 79 | 80 | # Record stereo sound from the line in or microphone and save it as mp3 file 81 | 82 | # Specify length and output file name 83 | 84 | # http://pymedia.org/ 85 | 86 | if __name__ == "__main__": 87 | import time 88 | if len( sys.argv )!= 2: 89 | print 'Usage: %s ' % sys.argv[ 0 ] 90 | else: 91 | voiceRecorder( sys.argv[ 1 ] ).run() 92 | -------------------------------------------------------------------------------- /pyvnc2swf/swf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ## 3 | ## pyvnc2swf - swf.py 4 | ## 5 | ## $Id: swf.py,v 1.6 2008/11/16 02:39:40 euske Exp $ 6 | ## 7 | ## Copyright (C) 2005 by Yusuke Shinyama (yusuke at cs . nyu . edu) 8 | ## All Rights Reserved. 9 | ## 10 | ## This is free software; you can redistribute it and/or modify 11 | ## it under the terms of the GNU General Public License as published by 12 | ## the Free Software Foundation; either version 2 of the License, or 13 | ## (at your option) any later version. 14 | ## 15 | ## This software is distributed in the hope that it will be useful, 16 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | ## GNU General Public License for more details. 19 | ## 20 | ## You should have received a copy of the GNU General Public License 21 | ## along with this software; if not, write to the Free Software 22 | ## Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 23 | ## USA. 24 | ## 25 | 26 | import sys, zlib 27 | from struct import pack, unpack 28 | try: 29 | from cStringIO import StringIO 30 | except ImportError: 31 | from StringIO import StringIO 32 | stderr = sys.stderr 33 | lowerbound = max 34 | upperbound = min 35 | 36 | CURSOR_DEPTH = 65535 37 | 38 | 39 | ## DataParser 40 | ## 41 | class DataParser: 42 | 43 | """ 44 | Low-level byte sequence parser. Inheritable. 45 | """ 46 | 47 | def __init__(self, debug=0): 48 | self.fp = None 49 | self.buff = 0 50 | self.bpos = 8 51 | self.debug = debug 52 | return 53 | 54 | def open(self, fname): 55 | self.fp = file(fname, 'rb') 56 | return 57 | 58 | # fixed bytes read 59 | 60 | def read(self, n): 61 | x = self.fp.read(n) 62 | if len(x) != n: 63 | raise EOFError 64 | return x 65 | 66 | def readui8(self): 67 | return ord(self.read(1)) 68 | def readsi8(self): 69 | return unpack('H', self.read(2))[0] 75 | def readsi16(self): 76 | return unpack('L', '\x00'+self.read(3))[0] 80 | 81 | def readui32(self): 82 | return unpack('L', self.read(4))[0] 85 | 86 | def readrgb(self): 87 | return ( self.readui8(), self.readui8(), self.readui8() ) 88 | def readrgba(self): 89 | return ( self.readui8(), self.readui8(), self.readui8(), self.readui8() ) 90 | 91 | # fixed bits read 92 | 93 | def setbuff(self, bpos=8, buff=0): 94 | (self.bpos, self.buff) = (bpos, buff) 95 | return 96 | 97 | def readbits(self, bits, signed=False): 98 | if bits == 0: return 0 99 | bits0 = bits 100 | v = 0 101 | while 1: 102 | r = 8-self.bpos # the number of remaining bits we can get from the current buffer. 103 | if bits <= r: 104 | # |-----8-bits-----| 105 | # |-bpos-|-bits-| | 106 | # | |----r----| 107 | v = (v<>(r-bits)) & ((1<>(bits0-1)): 119 | v -= (1<>stderr, 'Scanning source swf file: %s...' % fname 152 | pos = self.fp.tell() 153 | try: 154 | while 1: 155 | x = self.readui16() 156 | tag = x>>6 157 | if x & 63 == 63: 158 | length = self.readui32() 159 | else: 160 | length = x & 63 161 | pos0 = self.fp.tell() 162 | name = 'scan_tag%d' % tag 163 | # branch to scan_tag 164 | if hasattr(self, name): 165 | getattr(self, name)(tag, length) 166 | if self.debug: 167 | data = self.fp.read(length) 168 | print >>stderr, 'tag=%d, data=%r' % (tag, data) 169 | else: 170 | self.fp.seek(pos0+length) 171 | if tag == 1: 172 | # next frame 173 | self.framepos.append(pos) 174 | pos = self.fp.tell() 175 | except EOFError: 176 | pass 177 | return 178 | 179 | def parse_header(self): 180 | (F,W,S,V) = self.read(4) 181 | assert W+S == 'WS' 182 | self.swf_version = ord(V) 183 | if 6 <= self.swf_version: 184 | self.encoding = 'utf-8' 185 | self.totallen = self.readui32() 186 | if self.debug: 187 | print >>stderr, 'Header:', (F,W,S,self.swf_version,self.totallen) 188 | if F == 'C': 189 | # compressed 190 | x = zlib.decompress(self.fp.read()) 191 | self.fp = StringIO(x) 192 | self.rect = self.readrect() 193 | self.framerate = self.readui16()/256.0 194 | self.framecount = self.readui16() 195 | if self.debug: 196 | print >>stderr, 'Header:', self.rect, self.framerate, self.framecount 197 | return 198 | 199 | def parse_frame(self, n): 200 | self.fp.seek(self.framepos[n]) 201 | if self.debug: 202 | print >>stderr, 'seek:', n, self.framepos[n] 203 | try: 204 | while 1: 205 | x = self.readui16() 206 | tag = x>>6 207 | if x & 63 == 63: 208 | length = self.readui32() 209 | else: 210 | length = x & 63 211 | pos0 = self.fp.tell() 212 | name = 'do_tag%d' % tag 213 | # branch to do_tag 214 | if hasattr(self, name): 215 | getattr(self, name)(tag, length) 216 | else: 217 | self.do_unknown_tag(tag, length) 218 | self.fp.seek(pos0+length) 219 | if tag == 1: break 220 | except EOFError: 221 | pass 222 | return 223 | 224 | def parse_tag1(self): 225 | x = self.readui16() 226 | tag = x>>6 227 | if x & 63 == 63: 228 | length = self.readui32() 229 | else: 230 | length = x & 63 231 | pos0 = self.fp.tell() 232 | name = 'do_tag%d' % tag 233 | # branch to do_tag 234 | if hasattr(self, name): 235 | getattr(self, name)(tag, length) 236 | else: 237 | self.do_unknown_tag(tag, length) 238 | self.fp.seek(pos0+length) 239 | return 240 | 241 | def do_unknown_tag(self, tag, length): 242 | if self.debug: 243 | print >>stderr, 'unknown tag: %d, length=%d' % (tag, length) 244 | return 245 | 246 | def do_tag0(self, tag, length): 247 | # end 248 | return 249 | 250 | def readrect(self): 251 | '''(xmin, xmax, ymin, ymax) NOT width and height!''' 252 | x = ord(self.read(1)) 253 | bits = x>>3 254 | self.setbuff(5, x) 255 | return ( self.readbits(bits,1), self.readbits(bits,1), self.readbits(bits,1), self.readbits(bits,1) ) 256 | 257 | def readmatrix(self): 258 | '''returns (scalex, scaley, rot0, rot1, transx, transy)''' 259 | self.setbuff() 260 | (scalex, scaley) = (None, None) 261 | if self.readbits(1): # hasscale 262 | n = self.readbits(5) 263 | scalex = self.readbits(n,1)/65536.0 264 | scaley = self.readbits(n,1)/65536.0 265 | (rot0, rot1) = (None, None) 266 | if self.readbits(1): # hasrotate 267 | n = self.readbits(5) 268 | rot0 = self.readbits(n,1)/65536.0 269 | rot1 = self.readbits(n,1)/65536.0 270 | (transx, transy) = (None, None) 271 | n = self.readbits(5) 272 | transx = self.readbits(n,1) 273 | transy = self.readbits(n,1) 274 | return (scalex, scaley, rot0, rot1, transx, transy) 275 | 276 | def readgradient(self, version): 277 | n = self.readui8() 278 | r = [] 279 | for i in xrange(n): 280 | ratio = self.readui8() 281 | if version < 3: 282 | color = self.readrgb() 283 | else: 284 | color = self.readrgba() 285 | r.append((ratio, color)) 286 | return r 287 | 288 | def read_style(self, version): 289 | ''' 290 | fillstyles: list of (color, matrix, gradient, bitmapid, bitmapmatrix) 291 | linestyles: list of (width, color) 292 | ''' 293 | # fillstylearray 294 | fillstyles = [] 295 | nfills = self.readui8() 296 | if 2 <= version and nfills == 0xff: 297 | nfills = self.readui16() 298 | for i in xrange(nfills): 299 | t = self.readui8() 300 | (color, matrix, gradient, bitmapid, bitmapmatrix) = (None, None, None, None, None) 301 | if t == 0x00: 302 | if version == 3: 303 | color = self.readrgba() 304 | else: 305 | color = self.readrgb() 306 | elif t in (0x10, 0x12): 307 | matrix = self.readmatrix() 308 | gradient = self.readgradient(version) 309 | elif t in (0x40, 0x41, 0x42, 0x43): 310 | bitmapid = self.readui16() 311 | bitmapmatrix = self.readmatrix() 312 | fillstyles.append((color, matrix, gradient, bitmapid, bitmapmatrix)) 313 | # linestylearray 314 | linestyles = [] 315 | nlines = self.readui8() 316 | if 2 <= version and nlines == 0xff: 317 | nlines = self.readui16() 318 | for i in xrange(nlines): 319 | width = self.readui16() 320 | if version == 3: 321 | color = self.readrgba() 322 | else: 323 | color = self.readrgb() 324 | linestyles.append((width, color)) 325 | return (fillstyles, linestyles) 326 | 327 | def read_shape(self, version, fillstyles=[], linestyles=[]): 328 | self.setbuff() 329 | nfillbits = self.readbits(4) 330 | nlinebits = self.readbits(4) 331 | r = [] 332 | while 1: 333 | typeflag = self.readbits(1) 334 | if typeflag: 335 | # edge spec. 336 | straightflag = self.readbits(1) 337 | if straightflag: 338 | # StraightEdgeRecord 339 | n = self.readbits(4)+2 340 | if self.readbits(1): 341 | dx = self.readbits(n,1) 342 | dy = self.readbits(n,1) 343 | r.append( (1,(dx,dy)) ) 344 | elif self.readbits(1): 345 | dy = self.readbits(n,1) 346 | r.append( (1,(0,dy)) ) 347 | else: 348 | dx = self.readbits(n,1) 349 | r.append( (1,(dx,0)) ) 350 | else: 351 | # CurveEdgeRecord 352 | n = self.readbits(4)+2 353 | cx = self.readbits(n,1) 354 | cy = self.readbits(n,1) 355 | ax = self.readbits(n,1) 356 | ay = self.readbits(n,1) 357 | r.append( (2,(cx,cy),(ax,ay)) ) 358 | else: 359 | # style spec. 360 | flags = self.readbits(5) 361 | if flags == 0: break 362 | if flags & 1: 363 | n = self.readbits(5) 364 | x0 = self.readbits(n,1) 365 | y0 = self.readbits(n,1) 366 | r.append( (0,(x0,y0)) ) 367 | if flags & 2: 368 | fillstyle0 = self.readbits(nfillbits) 369 | if flags & 4: 370 | fillstyle1 = self.readbits(nfillbits) 371 | if flags & 8: 372 | linestyle1 = self.readbits(nlinebits) 373 | if flags & 16: 374 | (fillstyles, linestyles) = self.read_style(version) 375 | nfillbits = self.readbits(4) 376 | nlinebits = self.readbits(4) 377 | return r 378 | 379 | 380 | ## FLVParser 381 | ## 382 | class FLVParser(DataParser): 383 | 384 | def __init__(self, debug=0): 385 | DataParser.__init__(self, debug) 386 | self.tags = [] 387 | return 388 | 389 | def open(self, fname, header_only=False): 390 | DataParser.open(self, fname) 391 | self.parse_header() 392 | if header_only: return 393 | print >>stderr, 'Scanning source flv file: %s...' % fname 394 | try: 395 | offset = self.readub32() # always 0 396 | while 1: 397 | tag = self.readui8() 398 | length = self.readub24() 399 | timestamp = self.readub24() # timestamp in msec. 400 | reserved = self.readub32() 401 | offset = self.fp.tell() 402 | self.tags.append((tag, length, timestamp, offset)) 403 | self.fp.seek(offset + length + 4) # skip PreviousTagSize 404 | except EOFError: 405 | pass 406 | return 407 | 408 | def parse_header(self): 409 | (F,L,V,ver) = self.read(4) 410 | assert F+L+V == 'FLV' 411 | self.flv_version = ord(ver) 412 | flags = self.readui8() 413 | offset = self.readub32() 414 | if self.debug: 415 | print >>stderr, 'Header:', (F,L,V,self.flv_version,flags) 416 | return 417 | 418 | def get_tag(self, i): 419 | (tag, length, timestamp, offset) = self.tags[i] 420 | self.fp.seek(offset) 421 | data = self.read(length) 422 | return (tag, timestamp, data) 423 | 424 | def seek_tag(self, t): 425 | i0 = 0 426 | i1 = len(self.tags) 427 | while i0 < i1: 428 | i = (i0+i1)/2 429 | (tag, length, timestamp, offset) = self.tags[i] 430 | if timestamp < t: 431 | i0 = i 432 | else: 433 | i1 = i 434 | return (tag, length, timestamp, offset) 435 | 436 | 437 | ## SWFWriter 438 | ## 439 | 440 | # return the number of required bits for x. 441 | def needbits1(x, signed=False): 442 | if x == 0: 443 | return 0 444 | if signed: 445 | n = 1 446 | if x < 0: 447 | x = -x-1 448 | else: 449 | n = 0 450 | assert 0 < x 451 | while 1: 452 | n += 1 453 | x >>= 1 454 | if x == 0: break 455 | return n 456 | 457 | def needbits(args, signed=False): 458 | return max([ needbits1(x, signed) for x in args ]) 459 | # assert needbits1(0,0) == 0 460 | # assert needbits1(0,1) == 0 461 | # assert needbits1(1,0) == 1 462 | # assert needbits1(1,1) == 2 463 | # assert needbits1(2,0) == 2 464 | # assert needbits1(-2,1) == 2 465 | # assert needbits1(-3,1) == 3 466 | # assert needbits1(127,0) == 7 467 | # assert needbits1(127,1) == 8 468 | # assert needbits1(128,0) == 8 469 | # assert needbits1(-128,1) == 8 470 | # assert needbits1(-129,1) == 9 471 | # assert needbits1(-6380,1) == 14 472 | 473 | ## DataWriter 474 | ## A common superclass for SWFWriter and FLVWriter 475 | ## 476 | class DataWriter: 477 | 478 | def push(self): 479 | self.fpstack.append(self.fp) 480 | self.fp = StringIO() 481 | return 482 | 483 | def pop(self): 484 | assert self.fpstack, 'empty fpstack' 485 | self.fp.seek(0) 486 | data = self.fp.read() 487 | self.fp = self.fpstack.pop() 488 | return data 489 | 490 | # fixed bytes write 491 | 492 | def write(self, *args): 493 | for x in args: 494 | self.fp.write(x) 495 | return 496 | 497 | def writeui8(self, *args): 498 | for x in args: 499 | self.fp.write(chr(x)) 500 | return 501 | def writesi8(self, *args): 502 | for x in args: 503 | self.fp.write(pack('H', x)) 513 | return 514 | 515 | def writesi16(self, *args): 516 | for x in args: 517 | self.fp.write(pack('L', x)[1:4]) 523 | return 524 | 525 | def writeui32(self, *args): 526 | for x in args: 527 | self.fp.write(pack('L', x)) 532 | return 533 | 534 | def writergb(self, (r,g,b)): 535 | self.writeui8(r,g,b) 536 | return 537 | def writergba(self, (r,g,b,a)): 538 | self.writeui8(r,g,b,a) 539 | return 540 | 541 | # fixed bits write 542 | def writebits(self, bits, x, signed=False): 543 | if signed and x < 0: 544 | x += (1<> (bits-r)))) # r < bits 560 | self.buff = 0 561 | self.bpos = 0 562 | bits -= r # cut the upper r bits 563 | x &= (1< 833 | ## 834 | class FLVWriter(DataWriter): 835 | 836 | def __init__(self, outfile, flv_version, rect, framerate): 837 | if outfile == '-': 838 | self.outfp = sys.stdout 839 | else: 840 | self.outfp = file(outfile, 'wb') 841 | self.rect = rect 842 | self.flv_version = flv_version 843 | self.framerate = framerate 844 | self.fpstack = [] 845 | self.bpos = 0 846 | self.buff = 0 847 | self.fp = self.outfp 848 | self.fp.write('FLV%c' % self.flv_version) 849 | self.writebits(5,0) 850 | self.writebits(1,0) # has audio 851 | self.writebits(1,0) 852 | self.writebits(1,1) # has video 853 | self.finishbits() 854 | self.writeub32(9) # dataoffset (header size) = 9 855 | self.writeub32(0) # previous tag size = 0 856 | return 857 | 858 | def end_tag(self, tag, timestamp): 859 | data = self.pop() 860 | self.writeui8(tag) 861 | self.writeub24(len(data)) 862 | self.writeub24(int(timestamp)) 863 | self.writeui32(0) # reserved 864 | self.write(data) 865 | self.writeub32(len(data)+11) #size of this tag 866 | return 867 | 868 | def write_file(self, framecount): 869 | self.outfp.close() 870 | return 871 | 872 | 873 | # test 874 | if __name__ == "__main__": 875 | parser = FLVParser(True) 876 | parser.open(sys.argv[1]) 877 | -------------------------------------------------------------------------------- /pyvnc2swf/vnc2swf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ## 3 | ## pyvnc2swf - vnc2swf.py 4 | ## 5 | ## $Id: vnc2swf.py,v 1.7 2008/11/16 02:39:40 euske Exp $ 6 | ## 7 | ## Copyright (C) 2005 by Yusuke Shinyama (yusuke at cs . nyu . edu) 8 | ## All Rights Reserved. 9 | ## 10 | ## This is free software; you can redistribute it and/or modify 11 | ## it under the terms of the GNU General Public License as published by 12 | ## the Free Software Foundation; either version 2 of the License, or 13 | ## (at your option) any later version. 14 | ## 15 | ## This software is distributed in the hope that it will be useful, 16 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | ## GNU General Public License for more details. 19 | ## 20 | ## You should have received a copy of the GNU General Public License 21 | ## along with this software; if not, write to the Free Software 22 | ## Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 23 | ## USA. 24 | ## 25 | 26 | import sys, os, os.path, time, socket, re 27 | import Tkinter, tkFileDialog, tkMessageBox, tempfile, shutil 28 | from tkSimpleDialog import Dialog 29 | from struct import pack, unpack 30 | import threading 31 | 32 | from movie import SWFInfo 33 | from output import StreamFactory 34 | from rfb import RFBError, RFBNetworkClient, RFBFileParser, RFBNetworkClientForRecording, RFBStreamConverter 35 | stderr = sys.stderr 36 | 37 | 38 | ## tkPasswordDialog 39 | ## 40 | class tkPasswordDialog(Dialog): 41 | 42 | def __init__(self, title, prompt, master=None): 43 | if not master: 44 | master = Tkinter._default_root 45 | self.prompt = prompt 46 | Dialog.__init__(self, master, title) 47 | return 48 | 49 | def destroy(self): 50 | self.entry = None 51 | Dialog.destroy(self) 52 | return 53 | 54 | def body(self, master): 55 | w = Tkinter.Label(master, text=self.prompt, justify=Tkinter.LEFT) 56 | w.grid(row=0, padx=5, sticky=Tkinter.W) 57 | self.entry = Tkinter.Entry(master, name="entry", show="*") 58 | self.entry.grid(row=1, padx=5, sticky=Tkinter.W+Tkinter.E) 59 | return self.entry 60 | 61 | def validate(self): 62 | self.result = self.entry.get() 63 | return 1 64 | 65 | 66 | ## tkEntryDialog 67 | ## 68 | class tkEntryDialog(Dialog): 69 | 70 | def __init__(self, title, prompt, pattern=None, default=None, master=None): 71 | if not master: 72 | master = Tkinter._default_root 73 | self.prompt = prompt 74 | self.default = default 75 | self.pattern = re.compile(pattern) 76 | Dialog.__init__(self, master, title) 77 | return 78 | 79 | def destroy(self): 80 | self.entry = None 81 | Dialog.destroy(self) 82 | return 83 | 84 | def body(self, master): 85 | w = Tkinter.Label(master, text=self.prompt, justify=Tkinter.LEFT) 86 | w.grid(row=0, padx=5, sticky=Tkinter.W) 87 | self.entry = Tkinter.Entry(master, name="entry") 88 | self.entry.grid(row=1, padx=5, sticky=Tkinter.W+Tkinter.E) 89 | if self.default: 90 | self.entry.insert(0, self.default) 91 | return self.entry 92 | 93 | def validate(self): 94 | self.result = self.entry.get() 95 | if self.pattern and not self.pattern.match(self.result): 96 | return 0 97 | return 1 98 | 99 | 100 | ## RFBNetworkClientWithTkMixin 101 | ## 102 | class RFBNetworkClientWithTkMixin: 103 | 104 | def tk_init(self, root): 105 | self.root = root 106 | self.doloop = True 107 | return 108 | 109 | def interrupt(self): 110 | self.doloop = False 111 | return 112 | 113 | def loop(self): 114 | self.doloop = True 115 | while self.doloop: 116 | self.root.update() 117 | if not self.loop1(): break 118 | self.finish_update() 119 | return self 120 | 121 | def getpass(self): 122 | return tkPasswordDialog('Login', 123 | 'Password for %s:%d' % (self.host, self.port), 124 | self.root).result 125 | 126 | class RFBNetworkClientWithTk(RFBNetworkClientWithTkMixin, RFBNetworkClient): pass 127 | class RFBNetworkClientForRecordingWithTk(RFBNetworkClientWithTkMixin, RFBNetworkClientForRecording): pass 128 | 129 | 130 | ## VNC2SWFWithTk 131 | ## 132 | class VNC2SWFWithTk: 133 | 134 | FILE_TYPES = [ 135 | ('Flash(v5)', 'swf5', 'Macromedia Flash Files', '.swf'), # 0 136 | ('Flash(v7)', 'swf7', 'Macromedia Flash Files', '.swf'), # 1 137 | ('FLV', 'flv', 'Macromedia Flash Video Files', '.flv'), # 3 138 | ('MPEG', 'mpeg', 'MPEG Files', '.mpeg'), # 2 139 | ('VNCRec', 'vnc', 'VNCRec Files', '.vnc'), # 4 140 | ('noVNC', 'novnc', 'noVNC Files', '.novnc'), # 5 141 | ] 142 | 143 | def __init__(self, tempdir, info, 144 | outtype='swf5', host='localhost', port=5900, 145 | preferred_encoding=(0,), subprocess=None, pwdfile=None, 146 | debug=0): 147 | self.tempdir = tempdir 148 | self.moviefile = info.filename 149 | self.info = info 150 | self.debug = debug 151 | self.preferred_encoding = preferred_encoding 152 | self.subprocess = subprocess 153 | self.pwdfile = pwdfile 154 | self.outtype = outtype 155 | self.host = host 156 | self.port = port 157 | self.recording = False 158 | self.exit_immediately = False 159 | self.root = Tkinter.Tk() 160 | self.root.title('vnc2swf.py') 161 | self.root.wm_protocol('WM_DELETE_WINDOW', self.file_exit) 162 | self.toggle_button = Tkinter.Button(master=self.root) 163 | self.toggle_button.pack(side=Tkinter.LEFT) 164 | self.status_label = Tkinter.Label(master=self.root, justify=Tkinter.LEFT) 165 | self.status_label.pack(side=Tkinter.LEFT) 166 | self.setup_menubar() 167 | self.file_new(True) 168 | return 169 | 170 | def frames(self): 171 | return self.stream and self.stream.output_frames 172 | 173 | # Set up the GUI components. 174 | def setup_menubar(self): 175 | 176 | def option_server(): 177 | x = tkEntryDialog('Server', 'Server? (host:port)', pattern='^([^:/]+(:\d+)?|)$', 178 | default='%s:%d' % (self.host, self.port), master=self.root).result 179 | if not x: return 180 | m = re.match(r'^([^:/]*)(:(\d+))?', x) 181 | if not m: 182 | tkMessageBox.showerror('Invalid address: %s' % x) 183 | return 184 | (host, port) = (m.group(1) or 'localhost', int(m.group(3) or '5900')) 185 | if host != self.host or port != self.port and self.file_new_ask(): 186 | (self.host, self.port) = (host, port) 187 | self.file_new(True) 188 | return 189 | 190 | def option_framerate(): 191 | x = tkEntryDialog('Framerate', 'Framerate? (fps)', pattern='^([1-9][.0-9]+|)$', 192 | default=self.info.framerate, master=self.root).result 193 | if not x: return 194 | framerate = float(x) 195 | if framerate != self.info.framerate and self.file_new_ask(): 196 | self.info.framerate = framerate 197 | self.file_new(True) 198 | return 199 | 200 | def option_author(): 201 | x = tkEntryDialog('Author', 'Author? (abc )', pattern='^[a-zA-Z0-9 _.-]+ <[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+>$', 202 | default=self.info.author, master=self.root).result 203 | if not x: return 204 | author = x 205 | if author != self.info.author and self.file_new_ask(): 206 | self.info.author = author 207 | self.file_new(True) 208 | return 209 | 210 | def option_title(): 211 | x = tkEntryDialog('Title', 'Title?', pattern='^[a-zA-Z0-9 _.+-]+$', 212 | default=self.info.title, master=self.root).result 213 | if not x: return 214 | title = x 215 | if title != self.info.title and self.file_new_ask(): 216 | self.info.title = title 217 | self.file_new(True) 218 | return 219 | 220 | def option_category(): 221 | x = tkEntryDialog('Category', 'Category?', pattern='^[a-zA-Z0-9 ,]+$', 222 | default=self.info.category, master=self.root).result 223 | if not x: return 224 | category = x 225 | if category != self.info.category and self.file_new_ask(): 226 | self.info.category = category 227 | self.file_new(True) 228 | return 229 | 230 | def option_tags(): 231 | x = tkEntryDialog('Tags', 'Tags?', pattern='^[a-zA-Z0-9 ,]+$', 232 | default=self.info.tags, master=self.root).result 233 | if not x: return 234 | tags = x 235 | if tags != self.info.tags and self.file_new_ask(): 236 | self.info.tags = tags 237 | self.file_new(True) 238 | return 239 | 240 | def option_desc(): 241 | x = tkEntryDialog('Description', 'Description?', pattern='^[a-zA-Z0-9,. ]+$', 242 | default=self.info.desc, master=self.root).result 243 | if not x: return 244 | desc = x 245 | if desc != self.info.desc and self.file_new_ask(): 246 | self.info.desc = desc 247 | self.file_new(True) 248 | return 249 | 250 | def option_clipping(): 251 | try: 252 | s = self.info.get_clipping() 253 | except ValueError: 254 | s = '' 255 | x = tkEntryDialog('Clipping', 'Clipping? (ex. 640x480+0+0)', 256 | pattern='^(\d+x\d+\+\d+\+\d+|)$', 257 | default=s, master=self.root).result 258 | if not x: return 259 | if x != s and self.file_new_ask(): 260 | self.info.set_clipping(x) 261 | self.file_new(True) 262 | return 263 | 264 | record_type = Tkinter.StringVar(self.root) 265 | record_type.set(self.outtype) 266 | def option_type(): 267 | if record_type.get() != self.outtype and self.file_new_ask(): 268 | self.outtype = record_type.get() 269 | self.file_new() 270 | else: 271 | record_type.set(self.outtype) 272 | return 273 | 274 | menubar = Tkinter.Menu(self.root) 275 | self.file_menu = Tkinter.Menu(menubar, tearoff=0) 276 | self.file_menu.add_command(label="New...", underline=0, command=self.file_new, accelerator='Alt-N') 277 | self.file_menu.add_command(label="Save as...", underline=0, command=self.file_saveas, accelerator='Alt-S') 278 | self.file_menu.add_separator() 279 | self.file_menu.add_command(label="Exit", underline=1, command=self.file_exit) 280 | self.option_menu = Tkinter.Menu(menubar, tearoff=0) 281 | self.option_menu.add_command(label="Server...", underline=0, command=option_server) 282 | self.option_menu.add_command(label="Clipping...", underline=0, command=option_clipping) 283 | self.option_menu.add_command(label="Framerate...", underline=0, command=option_framerate) 284 | type_submenu = Tkinter.Menu(self.option_menu, tearoff=0) 285 | for (k,v,_,_) in self.FILE_TYPES: 286 | type_submenu.add_radiobutton(label=k, value=v, variable=record_type, command=option_type) 287 | self.option_menu.add_cascade(label="Type", underline=0, menu=type_submenu) 288 | self.option_menu.add_command(label="Author...", underline=0, command=option_author) 289 | self.option_menu.add_command(label="Title...", underline=0, command=option_title) 290 | self.option_menu.add_command(label="Category...", underline=0, command=option_category) 291 | self.option_menu.add_command(label="Tags...", underline=0, command=option_tags) 292 | self.option_menu.add_command(label="Description...", underline=0, command=option_desc) 293 | menubar.add_cascade(label="File", underline=0, menu=self.file_menu) 294 | menubar.add_cascade(label="Option", underline=0, menu=self.option_menu) 295 | self.root.config(menu=menubar) 296 | self.root.bind('', lambda e: self.file_new()) 297 | self.root.bind('', lambda e: self.file_saveas()) 298 | return 299 | 300 | # Change the current status of UI. 301 | def set_status(self): 302 | 303 | def enable_menus(state): 304 | self.file_menu.entryconfig(0, state=state) # "File->New..." 305 | self.option_menu.entryconfig(0, state=state) # "Option->Server..." 306 | self.option_menu.entryconfig(1, state=state) # "Option->Clipping..." 307 | self.option_menu.entryconfig(2, state=state) # "Option->Framerate..." 308 | self.option_menu.entryconfig(3, state=state) # "Option->Type" 309 | return 310 | 311 | # "File->Save As..." 312 | if not self.recording and self.frames(): 313 | self.file_menu.entryconfig(1, state='normal') 314 | else: 315 | self.file_menu.entryconfig(1, state='disabled') 316 | 317 | s = [] 318 | if not self.recording: 319 | s.append('Ready (%d frames recorded).' % (self.frames() or 0)) 320 | self.toggle_button.config(text='Start', underline=0) 321 | self.toggle_button.config(background='#80ff80', activebackground='#00ff00') 322 | self.toggle_button.config(command=self.record) 323 | self.root.bind('', lambda e: self.record()) 324 | self.root.bind('', lambda e: self.record()) 325 | enable_menus('normal') 326 | else: 327 | s.append('Recording...') 328 | self.toggle_button.config(text='Stop', underline=0) 329 | self.toggle_button.config(background='#80ff80', activebackground='#ff0000') 330 | self.toggle_button.config(command=self.client.interrupt) 331 | self.root.bind('', lambda e: self.client.interrupt()) 332 | self.root.bind('', lambda e: self.client.interrupt()) 333 | enable_menus('disabled') 334 | if self.host != 'localhost' or self.port != 5900: 335 | s.append('Server: %s:%d' % (self.host, self.port)) 336 | if self.info.clipping: 337 | s.append('Clipping: %s' % self.info.get_clipping()) 338 | if self.info.framerate: 339 | s.append('Framerate: %s' % self.info.framerate) 340 | self.status_label.config(text='\n'.join(s)) 341 | return 342 | 343 | # File->New 344 | def file_new_ask(self): 345 | if self.frames(): 346 | if not tkMessageBox.askokcancel('New file', 'Discard the current session?'): 347 | return False 348 | return True 349 | 350 | def file_new(self, force=False): 351 | if self.recording or (not force and not self.file_new_ask()): return 352 | ext = dict([ (t,ext) for (_,t,desc,ext) in self.FILE_TYPES ])[self.outtype] 353 | if self.moviefile: 354 | moviefile = self.moviefile 355 | else: 356 | moviefile = os.path.join(self.tempdir, 'pyvnc2swf-%d%s' % (os.getpid(), ext)) 357 | self.info.filename = moviefile 358 | self.fp = None 359 | if self.outtype == 'vnc' or self.outtype == 'novnc': 360 | self.fp = file(self.info.filename, 'wb') 361 | self.client = RFBNetworkClientForRecordingWithTk( 362 | self.host, self.port, self.fp, pwdfile=self.pwdfile, 363 | preferred_encoding=self.preferred_encoding, debug=self.debug, outtype=self.outtype, info=self.info) 364 | self.stream = None 365 | else: 366 | self.stream = StreamFactory(self.outtype)(self.info) 367 | self.client = RFBNetworkClientWithTk( 368 | self.host, self.port, RFBStreamConverter(self.info, self.stream), 369 | pwdfile=self.pwdfile, 370 | preferred_encoding=self.preferred_encoding) 371 | self.set_status() 372 | return True 373 | 374 | # File->SaveAs 375 | def file_saveas(self): 376 | if self.recording or not self.frames(): return 377 | (ext,desc) = dict([ (t,(ext,desc)) for (_,t,desc,ext) in self.FILE_TYPES ])[self.outtype] 378 | filename = tkFileDialog.asksaveasfilename( 379 | master=self.root, title='Vnc2swf Save As', defaultextension=ext, 380 | filetypes=[(desc,'*'+ext), ("All Files", "*")] 381 | ) 382 | if not filename: return 383 | if self.stream: 384 | # Finish the movie. 385 | self.stream.close() 386 | self.stream = None 387 | if self.fp: 388 | self.fp.close() 389 | self.fp = None 390 | shutil.move(self.info.filename, filename) 391 | self.info.write_html(filename=filename) 392 | self.set_status() 393 | return 394 | 395 | # File->Exit 396 | def file_exit(self): 397 | if self.recording: 398 | self.client.interrupt() 399 | self.exit_immediately = True 400 | else: 401 | if self.frames(): 402 | if not tkMessageBox.askokcancel('Exit', 'Discard the current session?'): 403 | return 404 | self.root.destroy() 405 | return 406 | 407 | #def stop_record(self): 408 | # if self.recording: 409 | # self.client.interrupt() 410 | 411 | # Do recording. 412 | def record(self): 413 | self.client.tk_init(self.root) 414 | #self.root.iconify() 415 | #if self.outtype == 'novnc': 416 | # self.root.bind('', lambda e: self.stop_record()) 417 | try: 418 | self.client.init().auth().start() 419 | except socket.error, e: 420 | return self.error('Socket error', e) 421 | except RFBError, e: 422 | return self.error('RFB protocol error', e) 423 | if self.debug: 424 | print >>stderr, 'start recording' 425 | self.recording = True 426 | self.set_status() 427 | if self.subprocess: 428 | self.subprocess.start() 429 | try: 430 | self.client.loop() 431 | except socket.error, e: 432 | return self.error('Socket error', e) 433 | except RFBError, e: 434 | return self.error('RFB protocol error', e) 435 | if self.debug: 436 | print >>stderr, 'stop recording' 437 | if self.subprocess: 438 | self.subprocess.stop() 439 | self.client.close() 440 | self.recording = False 441 | self.set_status() 442 | if self.exit_immediately: 443 | self.file_exit() 444 | if self.outtype == 'novnc': 445 | self.file_exit() 446 | return 447 | 448 | # Displays an error message. 449 | def error(self, msg, arg): 450 | print >>stderr, arg 451 | tkMessageBox.showerror('vnc2swf: %s' % msg, str(arg)) 452 | return 453 | 454 | # Runs Tk mainloop. 455 | def run(self): 456 | self.root.mainloop() 457 | return 458 | 459 | 460 | ## vnc2swf - CLI routine 461 | ## 462 | def vnc2swf(info, outtype='swf5', host='localhost', port=5900, 463 | preferred_encoding=(0,), subprocess=None, pwdfile=None, vncfile=None, 464 | debug=0, merge=False): 465 | fp = None 466 | if outtype == 'vnc' or outtype == 'novnc': 467 | if info.filename == '-': 468 | fp = sys.stdout 469 | else: 470 | fp = file(info.filename, 'wb') 471 | client = RFBNetworkClientForRecording(host, port, fp, pwdfile=pwdfile, 472 | preferred_encoding=preferred_encoding, debug=debug, outtype=outtype, info=info) 473 | else: 474 | stream = StreamFactory(outtype)(info, debug=debug) 475 | converter = RFBStreamConverter(info, stream, debug=debug) 476 | if vncfile: 477 | client = RFBFileParser(vncfile, converter, debug=debug) 478 | else: 479 | client = RFBNetworkClient(host, port, converter, pwdfile=pwdfile, 480 | preferred_encoding=preferred_encoding, debug=debug) 481 | try: 482 | client.init().auth().start() 483 | except socket.error, e: 484 | print >>stderr, 'Socket error:', e 485 | except RFBError, e: 486 | print >>stderr, 'RFB error:', e 487 | if debug: 488 | print >>stderr, 'start recording' 489 | if subprocess: 490 | subprocess.start() 491 | try: 492 | client.loop() 493 | except KeyboardInterrupt: 494 | pass 495 | except socket.error, e: 496 | print >>stderr, 'Socket error:', e 497 | except RFBError, e: 498 | print >>stderr, 'RFB error:', e 499 | if debug: 500 | print >>stderr, 'stop recording' 501 | if subprocess: 502 | subprocess.stop() 503 | client.close() 504 | try: 505 | stream.close() 506 | except: 507 | pass 508 | info.write_html() 509 | if fp: 510 | fp.close() 511 | # Contributed by David Fraser 512 | if merge: 513 | tmpfile = os.tempnam(os.path.dirname(info.filename), "vncmerge-") + os.path.basename(info.filename) 514 | print >>stderr, "renaming %s to %s for merge" % (info.filename, tmpfile) 515 | if os.path.exists(tmpfile): 516 | os.remove(tmpfile) 517 | os.rename( info.filename, tmpfile ) 518 | try: 519 | # TODO: sort out getting outputfilename properly 520 | audiofilename = subprocess.outputfile 521 | import edit 522 | args = ["-d", "-o", info.filename, "-a", audiofilename, tmpfile] 523 | if not edit.main(args): 524 | print >>stderr, "Error doing merge..." 525 | finally: 526 | origexists, tmpexists = os.path.exists(info.filename), os.path.exists(tmpfile) 527 | print >>stderr, "origexists %r, tmpexists %r" % (origexists, tmpexists) 528 | if origexists and tmpexists: 529 | try: 530 | os.remove(tmpfile) 531 | except OSError, e: 532 | print >>stderr, "Could not remove temporary file: %s" % e 533 | elif tmpexists: 534 | print >>stderr, "only tmpfile remains after merge, renaming to original (but will not contain sound)" 535 | os.rename( tmpfile, info.filename ) 536 | return 537 | 538 | 539 | # Thread management 540 | class RecordingThread: 541 | def __init__(self, outputfile): 542 | try: 543 | import record_sound 544 | except ImportError, e: 545 | print >>stderr, "unable to use pymedia?:", e 546 | raise 547 | self.outputfile = outputfile 548 | self.recorder = record_sound.voiceRecorder(self.outputfile) 549 | self.thread = threading.Thread(target=self.recorder.run) 550 | 551 | def start(self): 552 | self.thread.start() 553 | 554 | def stop(self): 555 | self.recorder.finished = True 556 | self.thread.join() 557 | 558 | # Subprocess management 559 | class Subprocess: 560 | 561 | def __init__(self, s): 562 | try: 563 | import subprocess 564 | except ImportError: 565 | print >>stderr, '-S option requires Python 2.4 or newer.' 566 | sys.exit(111) 567 | if not hasattr(os, 'kill'): 568 | print >>stderr, '-S option works only on Unix or Mac OS X.' 569 | sys.exit(111) 570 | self.args = s.split(' ') 571 | self.popen = None 572 | return 573 | 574 | def start(self): 575 | import subprocess 576 | self.popen = subprocess.Popen(self.args) 577 | return 578 | 579 | def stop(self): 580 | import signal 581 | os.kill(self.popen.pid, signal.SIGINT) 582 | self.popen.wait() 583 | return 584 | 585 | 586 | # main 587 | # ./vnc2swf.py -S 'arecord -t wav -c 1 -r 22050 out.wav' -n -o out.swf 588 | def main(argv): 589 | import getopt 590 | def usage(): 591 | print ('usage: %s [-d] [-n] [-o filename] [-t {flv|mpeg|swf5|swf7|vnc|novnc}]' 592 | ' [-e encoding] [-N] [-C clipping] [-r framerate] [-s scaling] [-z] [-m] [-a] [-V]' 593 | ' [-A author] [-T title] [-c Categories] [-G tags] [-D description]' 594 | ' [-S subprocess] [-P pwdfile] [host[:display] [port]]' % argv[0]) 595 | return 100 596 | try: 597 | (opts, args) = getopt.getopt(argv[1:], 'dno:t:e:NC:r:S:P:A:T:c:G:D:s:zmaV') 598 | except getopt.GetoptError: 599 | return usage() 600 | (debug, console, outtype, subprocess, merge, pwdfile, isfile) = (0, False, None, None, False, None, False) 601 | (cursor, host, port, preferred_encoding) = (True, 'localhost', 5900, (-260,0x05,0x02,0x00,-26,-247,-223,-224,-258,-308,-312,-313,)) 602 | #(cursor, host, port, preferred_encoding) = (True, 'localhost', 5900, (0x01,-260,0x05,0x02,0x00,-26,-247,-223,-224,-258,-308,-312,-313,)) 603 | info = SWFInfo() 604 | for (k, v) in opts: 605 | if k == '-d': debug += 1 606 | elif k == '-n': console = True 607 | elif k == '-t': outtype = v 608 | elif k == '-e': preferred_encoding = tuple([ int(i) for i in v.split(',') ]) 609 | elif k == '-N': cursor = False 610 | elif k == '-S': subprocess = Subprocess(v) 611 | elif k == '-a': subprocess = RecordingThread(v) 612 | elif k == '-m': merge = True 613 | elif k == '-P': pwdfile = v 614 | elif k == '-V': isfile = True 615 | elif k == '-o': 616 | info.filename = v 617 | elif k == '-C': 618 | try: 619 | info.set_clipping(v) 620 | except ValueError: 621 | print 'Invalid clipping specification:', v 622 | return usage() 623 | elif k == "-r": 624 | info.framerate = int(v) 625 | elif k == "-A": 626 | info.author = v 627 | elif k == "-T": 628 | info.title = v 629 | elif k == "-c": 630 | info.category = v 631 | elif k == "-G": 632 | info.tags = v 633 | elif k == "-D": 634 | info.desc = v 635 | elif k == "-z": 636 | info.set_scalable(True) 637 | elif k == '-s': 638 | info.scaling = float(v) 639 | assert 0 < info.scaling and info.scaling <= 1.0, 'Invalid scaling.' 640 | if not outtype: 641 | if info.filename: 642 | if info.filename.endswith('.vnc'): 643 | outtype = 'vnc' 644 | elif info.filename.endswith('.novnc'): 645 | outtype = 'novnc' 646 | elif info.filename.endswith('.swf'): 647 | outtype = 'swf5' 648 | elif info.filename.endswith('.mpg') or info.filename.endswith('.mpeg'): 649 | outtype = 'mpeg' 650 | elif info.filename.endswith('.flv'): 651 | outtype = 'flv' 652 | else: 653 | outtype = 'swf5' 654 | if outtype not in ('swf5','swf7','vnc','mpeg','flv', 'novnc'): 655 | print 'Please specify the output type or file extension.' 656 | return usage() 657 | if not cursor: 658 | preferred_encoding += (-239,) 659 | if 1 <= len(args): 660 | if ':' in args[0]: 661 | i = args[0].index(':') 662 | host = args[0][:i] or 'localhost' 663 | port = int(args[0][i+1:])+5900 664 | else: 665 | host = args[0] 666 | if 2 <= len(args): 667 | port = int(args[1]) 668 | if console: 669 | if not info.filename: 670 | print 'Please specify the output filename.' 671 | return usage() 672 | vncfile = None 673 | if isfile: 674 | vncfile = sys.stdin 675 | if args: 676 | vncfile = file(args[0], 'rb') 677 | vnc2swf(info, outtype, host, port, 678 | preferred_encoding=preferred_encoding, 679 | subprocess=subprocess, pwdfile=pwdfile, vncfile=vncfile, 680 | merge=merge, debug=debug) 681 | else: 682 | tempdir = os.path.join(tempfile.gettempdir(), 'pyvnc2swf') 683 | try: 684 | os.mkdir(tempdir) 685 | except OSError: 686 | pass 687 | VNC2SWFWithTk(tempdir, info, outtype, host, port, 688 | preferred_encoding=preferred_encoding, 689 | subprocess=subprocess, pwdfile=pwdfile, 690 | debug=debug).run() 691 | return 692 | 693 | if __name__ == "__main__": sys.exit(main(sys.argv)) 694 | -------------------------------------------------------------------------------- /recordings/.placeholder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinyclub/pyvnc2swf/fef3e6075f67954426e199a1f10902fa0e65c03a/recordings/.placeholder -------------------------------------------------------------------------------- /tools/.gitignore: -------------------------------------------------------------------------------- 1 | .vnc.pwd.file 2 | vnc.record.* 3 | -------------------------------------------------------------------------------- /tools/config: -------------------------------------------------------------------------------- 1 | VNC_SERVER=localhost 2 | VNC_PORT=5900 3 | VNC_PWD=ubuntu 4 | VNC_PWD_FILE=.vnc.pwd.file 5 | VNC_RECORDINGS=${TOP_DIR}/recordings/ 6 | VNC_RECORD_FILE=$(date -u +"%Y%m%d%H%M%S") 7 | VNC_SCREEN_SIZE=800x600 8 | VNC_CURSOR=1 9 | VNC_CONSOLE=0 10 | VNC_FPS=60 11 | 12 | VNC_frame_author="Wu Zhangjin " 13 | #VNC_frame_title="Linux Lab" 14 | #VNC_frame_tags="Linux, Lab" 15 | #VNC_frame_desc="A Lab for Linux OS Learning." 16 | 17 | # Export for noVNC recording ... 18 | export VNC_frame_author VNC_frame_title VNC_frame_tags VNC_frame_desc 19 | -------------------------------------------------------------------------------- /tools/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # install.sh -- install record/play shortcuts to Desktop 4 | # 5 | 6 | TOP_DIR=$(cd $(dirname $0) && pwd)/.. 7 | 8 | NOVNC_RECORD=${TOP_DIR}/tools/novnc-record.sh 9 | NOVNC_PLAY=${TOP_DIR}/tools/play.sh 10 | NOVNC_RECORD_ICON=${TOP_DIR}/icons/recorder.png 11 | NOVNC_PLAY_ICON=${TOP_DIR}/icons/player.png 12 | 13 | cat < ~/Desktop/vnc-record.desktop 14 | [Desktop Entry] 15 | Encoding=UTF-8 16 | Name=noVNC REC 17 | Comment=Record the VNC Session with noVNC output 18 | Exec=$NOVNC_RECORD 19 | Icon=$NOVNC_RECORD_ICON 20 | Type=Application 21 | EOF 22 | 23 | cat < ~/Desktop/vnc-play.desktop 24 | [Desktop Entry] 25 | Encoding=UTF-8 26 | Name=noVNC Player 27 | Comment=Play the just recorded VNC session 28 | Exec=$NOVNC_PLAY 29 | Icon=$NOVNC_PLAY_ICON 30 | Type=Application 31 | EOF 32 | -------------------------------------------------------------------------------- /tools/novnc-record.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | TOP_DIR=$(cd $(dirname $0) && pwd)/ 4 | 5 | OUTTYPE=novnc 6 | 7 | . ${TOP_DIR}/record.sh 8 | -------------------------------------------------------------------------------- /tools/play.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # play.sh -- play the latest vnc session 4 | # 5 | 6 | TOP_DIR=$(cd $(dirname $0) && pwd)/../ 7 | 8 | PLAYER=${TOP_DIR}/pyvnc2swf/play.py 9 | TOOLS=${TOP_DIR}/tools/ 10 | CONFIG=${TOOLS}/config 11 | 12 | . ${CONFIG} 13 | 14 | FPS="" 15 | VNC_RECORD_FILE="" 16 | 17 | [ -z "$RECORDINGS" ] && RECORDINGS=$VNC_RECORDINGS 18 | [ -z "$FPS" ] && FPS=5 19 | [ -n "$FPS" ] && FPS_OPT=" -r $FPS " 20 | 21 | [ -z "$DEBUG" ] && DEBUG=0 22 | [ $DEBUG -eq 1 ] && DEBUG_OPT=" -d " 23 | 24 | [ -n "$1" ] && VNC_RECORD_FILE=$1 25 | [ -z "$VNC_RECORD_FILE" ] && VNC_RECORD_FILE="$(ls -rt `find ${RECORDINGS} -type f -name '*vnc'` | tail -1)" 26 | !(echo $VNC_RECORD_FILE | grep -q "vnc$") && \ 27 | echo "Error: no vnc session with .novnc or .vnc suffix exist." && exit 1 28 | VNC_RECORD_FILE=$(cd $(dirname $VNC_RECORD_FILE) && pwd)/$(basename $VNC_RECORD_FILE) 29 | 30 | $PLAYER $FPS_OPT $DEBUG_OPT $VNC_RECORD_FILE 31 | -------------------------------------------------------------------------------- /tools/record.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # record.sh -- record vnc session 4 | # 5 | 6 | TOP_DIR=$(cd $(dirname $0) && pwd)/../ 7 | 8 | RECORDER=${TOP_DIR}/pyvnc2swf/vnc2swf.py 9 | TOOLS=${TOP_DIR}/tools/ 10 | CONFIG=${TOOLS}/config 11 | 12 | [ -z "$OUTTYPE" ] && OUTTYPE=vnc 13 | [ -z "$DEBUG" ] && DEBUG=0 14 | [ -z "$PASSWD" ] && PASSWD=1 15 | [ -z "$CURSOR" ] && CURSOR=1 16 | [ -z "$CONSOLE" ] && CONSOLE=0 17 | [ -z "$SERVER" ] && SERVER=1 && PASSWD=1 18 | 19 | . ${CONFIG} 20 | 21 | [ -z "$RECORDINGS" ] && RECORDINGS=$VNC_RECORDINGS 22 | [ -z "$CONSOLE" ] && CONSOLE=$VNC_CONSOLE 23 | [ -z "$CURSOR" ] && CURSOR=$VNC_CURSOR 24 | [ -z "$SCREEN_SIZE" ] && SCREEN_SIZE=$VNC_SCREEN_SIZE 25 | [ -z "$FPS" ] && FPS=$VNC_FPS 26 | 27 | VNC_TARGET=$1 28 | if [ -n "$VNC_TARGET" ]; then 29 | VNC_SERVER=${VNC_TARGET%:*} 30 | (echo $VNC_TARGET | grep -q ":") && VNC_PORT=${VNC_TARGET#*:} 31 | [ -z "$VNC_PORT" ] && VNC_PORT=5900 32 | fi 33 | 34 | VNC_PWD_FILE=${TOOLS}/$VNC_PWD_FILE 35 | VNC_PWD_FILE=$(cd $(dirname $VNC_PWD_FILE) && pwd)/$(basename $VNC_PWD_FILE) 36 | if [ $PASSWD -eq 1 ]; then 37 | x11vnc -storepasswd $VNC_PWD $VNC_PWD_FILE 38 | PASSWD_OPT=" -P $VNC_PWD_FILE " 39 | fi 40 | if [ $VNC_SERVER == "localhost" -a $SERVER -eq 1 ]; then 41 | ps -ef | grep -v grep | grep -q "x11vnc.*forever.*viewonly.*localhost" 42 | if [ $? -ne 0 ]; then 43 | #pkill x11vnc 44 | #x11vnc -scale $SCREEN_SIZE -quiet -cursor -viewonly -bg -localhost -wait 10 -defer 10 -rfbauth $VNC_PWD_FILE 45 | #x11vnc -quiet -cursor -viewonly -bg -localhost -rfbauth $VNC_PWD_FILE 46 | x11vnc -quiet -forever -viewonly -bg -localhost -rfbauth $VNC_PWD_FILE 47 | fi 48 | fi 49 | 50 | VNC_RECORD_DIR=${RECORDINGS}/`date -u +"%Y-%m-%d"` 51 | [ ! -d $VNC_RECORD_DIR ] && mkdir $VNC_RECORD_DIR 52 | 53 | VNC_RECORD_FILE=${VNC_RECORD_DIR}/${VNC_RECORD_FILE}.${OUTTYPE} 54 | VNC_RECORD_FILE=$(cd $(dirname $VNC_RECORD_FILE) && pwd)/$(basename $VNC_RECORD_FILE) 55 | OUTFILE=" -o $VNC_RECORD_FILE " 56 | 57 | CURSOR_OPT=" -N " 58 | [ "$CURSOR" == "1" ] && CURSOR_OPT="" 59 | 60 | CONSOLE_OPT="" 61 | [ "$CONSOLE" == "1" ] && CONSOLE_OPT=" -n " 62 | 63 | [ -n "$FPS" ] && FPS_OPT=" -r $FPS " 64 | [ $DEBUG -eq 1 ] && DEBUG_OPT=" -d " 65 | 66 | [ "$VNC_SERVER" == "localhost" ] && xrandr -s $SCREEN_SIZE 67 | $RECORDER $CURSOR_OPT $CONSOLE_OPT $FPS_OPT $DEBUG_OPT $PASSWD_OPT $OUTFILE -t $OUTTYPE $VNC_SERVER $VNC_PORT 68 | [ "$VNC_SERVER" == "localhost" ] && xrandr -s 0 69 | --------------------------------------------------------------------------------