From 68cb75777cd3a68f92cd4a6c5dc871c49f8d4094 Mon Sep 17 00:00:00 2001 From: Lincoln de Sousa Date: Sat, 9 Aug 2008 16:06:55 -0300 Subject: [PATCH] Sorry for delay to put it in a vcs, some hours ago this was not a project, but just a joke (taking a penalty card) --- generate-zooming-video.sh | 262 ++++++++++++++++++ gzv.glade | 542 ++++++++++++++++++++++++++++++++++++++ gzv.py | 232 ++++++++++++++++ 3 files changed, 1036 insertions(+) create mode 100755 generate-zooming-video.sh create mode 100644 gzv.glade create mode 100644 gzv.py diff --git a/generate-zooming-video.sh b/generate-zooming-video.sh new file mode 100755 index 0000000..8d0d645 --- /dev/null +++ b/generate-zooming-video.sh @@ -0,0 +1,262 @@ +#!/bin/bash + +# generate-zooming-video - make a video from a big picture zooming datails +# Copyright (C) 2008 Aurélio A. Heckert +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# Version 0.1 + +# Collect all args and create local variables: +for arg in "$@"; do + if ( echo -n $arg | grep -q '^--.*=' ); then + key="$( echo -n "$arg" | + sed 's/^--\([^=]*\)=.*$/\1/; s/[^a-zA-Z0-9]/_/' )" + val="$( echo -n "$arg" | + sed 's/^--\([^=]*\)=\(.*\)$/\2/' )" + eval "$key=\"$val\"" + fi + if ( echo -n $arg | grep -q '^--[^=]\+$' ); then + key="$( echo -n "$arg" | + sed 's/^--\(.*\)$/\1/; s/[^a-zA-Z0-9]/_/' )" + eval "$key=true" + fi +done + +if ! test -e "$pic"; then + echo "Picture missed" >&2 + exit 1 +fi + +eval "$( identify -format 'pic_w=%w\npic_h=%h' "$pic" )" + +if ! test -e "$data_file"; then + echo "Data file missed" >&2 + exit 1 +fi + +[ "$video_size" = "" ] && video_size=400x300 +video_w=$( echo $video_size | sed 's/^\([0-9]*\)x.*$/\1/' ) +video_h=$( echo $video_size | sed 's/^.*x\([0-9]*\)$/\1/' ) +if [ "$video_w" = "" -o "$video_h" = "" ]; then + echo "Video size missed" >&2 + exit 1 +fi + +output="$output_video" +replace_output="$replace_output_video" +if [ "$output" = "" ]; then + echo "Output video file missed" >&2 + exit 1 +fi + +if test -e "$output" -a "$replace_output" != "true"; then + echo -n "Replace \"$output\"? [y,n] " + read -sn 1 resp + resp=$( echo "$resp" | tr y Y ) + test "$resp" != "Y" && resp=N + echo $resp + if [ $resp = N ]; then + echo "So, please, restart with a new name." + exit 0 + fi +fi + +function calc() { + echo "$@" | bc -l | sed 's/^\(-\?\)\./\10./; s/^$/0/' +} + +function round() { + calc "$@" | sed 's/\..*$//' +} + +function test_calc() { + r=$( calc "$1" ) + if [ "$r" = "$2" ]; then + err=Ok + else + err=Error + fi + echo "$1 = $2 ($err) => $( round "$@" )" +} +#test_calc '0 + 0' +#test_calc '0 + 1' +#test_calc '3 / 98700000' +#test_calc '0.102030' +#test_calc '80 * 2' +#test_calc '80.8 * 2' +#test_calc '-123' +#test_calc '-.123' +#test_calc '-.0123' '-0.0123' +#test_calc '010 + 1' 11 +#test_calc '0010 + 1' 11 + +function beault_num() { + sed 's/\.\(.*[^0]\)\?0*$/.\1/; s/\.$//' +} + +# Video Size Delta: +vsd=$( calc "$video_h / $video_w" | beault_num ) + +[ "$zoom_out_pct" = "" ] && zoom_out_pct=50 +zoom_out_pct=$( calc "$zoom_out_pct / 100" ) + +# Zoom Out Size: +if [ "$zow" = "" -o "$zoh" = "" ]; then + zoh=$( round "$pic_h * $zoom_out_pct" ) + zow=$( round "$zoh / $vsd" ) + if [ $zow -gt $( round "$pic_w * $zoom_out_pct" ) ]; then + zow=$( round "$pic_w * $zoom_out_pct" ) + zoh=$( round "$zow * $vsd" ) + fi +fi + +[ "$frames_stoped" = "" ] && frames_stoped=20 +[ "$frames_moveing" = "" ] && frames_moveing=10 + +echo "Video configuration: + Picture: $pic + Pic Size: w:$pic_w h:$pic_h + Zoom Out Size: w:$zow h:$zoh + Frames in Detail: $frames_stoped + Frames Moving: $frames_moveing + Data file: $data_file + Output video: $output + Output Size: w:${video_w} h:${video_h} delta:$vsd +" + +work_dir=$( mktemp -d ) + +cat "$data_file" | ( # start frame generator loop + +f=0 +old_x=$( round "$pic_w / 2" ) +old_y=$( round "$pic_h / 2" ) + +oldz_h=$pic_h +oldz_w=$( round "$oldz_h / $vsd" ) +if [ $oldz_w -gt $pic_w ]; then + oldz_w=$pic_w + oldz_h=$( round "$oldz_w * $vsd" ) +fi + +# Variables: +# Generated with Linear function: +# step : Frame Step (Natural Numbers) +# sm_# : Step Moving in Frame Step (0..1 scale) +# Generated with Sigmoid function: +# szo_# : Step Old Zoom in Frame Step (0..1 scale) +# szn_# : Step New Zoom in Frame Step (0..1 scale) + +eval "$( perl -e " + \$e=2.7182818284; + for ( \$step=0; \$step<=$frames_moveing; \$step++ ) { + \$sm = \$step / $frames_moveing; + \$szo = \$e**(-(\$sm-0.25)*20) / ( 1 + ( \$e**(-(\$sm-0.25)*20) ) ); + \$szo = 1 if \$sm == 0; + \$szo = 0 if \$sm >= 0.5; + \$szn = \$e**((\$sm-0.75)*20) / ( 1 + ( \$e**((\$sm-0.75)*20) ) ); + \$szn = 1 if \$sm == 1; + \$szn = 0 if \$sm <= 0.5; + print \"sm_\$step=\$sm \t; szo_\$step=\$szo \t; szn_\$step=\$szn\n\" + } +" )" + +frame=0 + +function move_camera() { + mid_step=$( round "$frames_moveing / 2" ) + for step in $( seq 0 $frames_moveing ); do + let frame++ + eval "sm=\$sm_$step ; szo=\$szo_$step ; szn=\$szn_$step" + if [ $step -le $mid_step ]; then + zw=$( calc "( $oldz_w * $szo ) + ( $zow * (1-$szo) )" ) + zh=$( calc "( $oldz_h * $szo ) + ( $zoh * (1-$szo) )" ) + else + zw=$( calc "( $zow * (1-$szn) ) + ( $zoom_w * $szn )" ) + zh=$( calc "( $zoh * (1-$szn) ) + ( $zoom_h * $szn )" ) + fi + x=$( calc "( $old_x * (1-$sm) ) + ( $goto_x * $sm )" | beault_num ) + y=$( calc "( $old_y * (1-$sm) ) + ( $goto_y * $sm )" | beault_num ) + x1=$( round "$x - ( $zw / 2 )" ) + x2=$( round "$x + ( $zw / 2 )" ) + y1=$( round "$y - ( $zh / 2 )" ) + y2=$( round "$y + ( $zh / 2 )" ) + echo -e " frame:$frame \tstep:$step \tsm:$sm \tx:$x \ty:$y" + #echo -e " szo:$szo \tszn:$szn \tzw=$zw \tzh=$zh" + echo -e " x1=$x1 \t x2=$x2 \t y1=$y1 \t y2=$y2" + if [ $x1 -lt 0 ]; then + x2=$(( $x2 - $x1 )) + x1=0 + fi + if [ $x2 -gt $pic_w ]; then + x1=$(( $x1 - ( $x2 - $pic_w ) )) + x2=$pic_w + fi + if [ $y1 -lt 0 ]; then + y2=$(( $y2 - $y1 )) + y1=0 + fi + if [ $y2 -gt $pic_h ]; then + y1=$(( $y1 - ( $y2 - $pic_h ) )) + y2=$pic_h + fi + echo -e " x1=$x1 \t x2=$x2 \t y1=$y1 \t y2=$y2" + convert "$pic" \ + -crop $( round $zw )x$( round $zh )+$( round $x1 )+$( round $y1 ) \ + +repage -resize "${video_w}x${video_h}!" \ + -quality 100 $work_dir/f_$frame.jpg + done +} + +while read line; do + eval "$( echo "$line" | sed "s/'/\´/g; s/^\([^\s]*\),\([^\s]*\) \([^\s]*\) \(.*\)$/goto_x='\1'\ngoto_y='\2'\nzoom_w='\3'\nname='\4'/" )" #' + zoom_h=$( round "$zoom_w * $vsd" ) + echo "Zooming $name (${old_x},${old_y}:${oldz_w}x${oldz_h} -> ${goto_x},${goto_y}:${zoom_w}x${zoom_h})" + move_camera + last_frame=$frame + for step in $( seq 2 $frames_stoped ); do + let frame++ + # copy the frame + cp $work_dir/f_$last_frame.jpg $work_dir/f_$frame.jpg + done + echo "" + old_x=$goto_x + old_y=$goto_y + oldz_w=$zoom_w + oldz_h=$zoom_h +done + +goto_x=$( round "$pic_w / 2" ) +goto_y=$( round "$pic_h / 2" ) +zoom_h=$pic_h +zoom_w=$( round "$zoom_h / $vsd" ) +if [ $zoom_w -gt $zoom_w ]; then + zoom_w=$pic_w + zoom_h=$( round "$zoom_w * $vsd" ) +fi +echo "Zooming The End (${old_x},${old_y}:${oldz_w}x${oldz_h} -> ${goto_x},${goto_y}:${zoom_w}x${zoom_h})" +move_camera + +) # end frame generator loop + +echo "Generating the movie..." + +rm "$output" >&2 +if ffmpeg -i "$work_dir/f_%d.jpg" "$output"; then + echo " Done: $output" + vlc "$output" +else + echo Ups... +fi + +rm -r $work_dir + diff --git a/gzv.glade b/gzv.glade new file mode 100644 index 0000000..2c27280 --- /dev/null +++ b/gzv.glade @@ -0,0 +1,542 @@ + + + + + + Gzv + 640 + 480 + + + True + + + True + + + True + _File + True + + + True + + + True + gtk-new + True + True + + + + + True + gtk-open + True + True + + + + + True + gtk-save + True + True + + + + + True + gtk-save-as + True + True + + + + + True + + + + + True + gtk-quit + True + True + + + + + + + + + True + _Edit + True + + + True + + + True + gtk-preferences + True + True + + + + + + + + + True + _View + True + + + + + True + _Help + True + + + True + + + True + gtk-about + True + True + + + + + + + + + False + + + + + True + + + True + gtk-new + + + + False + + + + + True + gtk-open + + + False + + + + + True + + + False + False + + + + + True + gtk-execute + + + False + + + + + False + 1 + + + + + True + True + + + True + + + True + + + True + 0 + 4 + Focus points + + + + + True + True + True + GTK_RELIEF_NONE + 0 + + + 18 + True + gtk-close + 1 + + + + + False + False + 1 + + + + + False + False + + + + + 179 + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + + + True + True + False + True + True + + + + + 1 + + + + + True + 4 + + + True + True + True + Remove the selected focus point + 0 + + + True + gtk-remove + + + + + + + True + True + True + Save list of focus points + 0 + + + True + gtk-save + + + + + 1 + + + + + True + True + True + Move the selected focus point up + 0 + + + True + gtk-go-up + + + + + 2 + + + + + True + True + True + Move the selected focus point down + 0 + + + True + gtk-go-down + + + + + 3 + + + + + False + 4 + 2 + + + + + False + True + + + + + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + GTK_RESIZE_QUEUE + + + True + + + True + + + + + + + + + True + True + + + + + 2 + + + + + True + 2 + + + False + 3 + + + + + + + 5 + GTK_WIN_POS_CENTER_ON_PARENT + 300 + GDK_WINDOW_TYPE_HINT_DIALOG + False + + + True + 2 + + + True + 10 + + + True + 0 + GTK_SHADOW_NONE + + + True + 14 + + + True + + + True + + + False + + + + + + + + + True + 8 + <b>Image</b> + True + + + label_item + + + + + + + True + 0 + GTK_SHADOW_NONE + + + True + 14 + + + True + + + True + + + True + 0 + Width: + + + + + True + True + + + 1 + + + + + + + True + + + True + 0 + Height: + + + + + True + True + + + 1 + + + + + 1 + + + + + + + + + True + 8 + <b>Video Dimensions</b> + True + + + label_item + + + + + 1 + + + + + + + + 1 + + + + + True + GTK_BUTTONBOX_END + + + True + True + True + gtk-cancel + True + 0 + + + + + True + True + True + gtk-ok + True + 0 + + + 1 + + + + + False + GTK_PACK_END + + + + + + diff --git a/gzv.py b/gzv.py new file mode 100644 index 0000000..958d13b --- /dev/null +++ b/gzv.py @@ -0,0 +1,232 @@ +# gzv.py - an user interface to generate-zooming-video +# +# Copyright (C) 2008 Lincoln de Sousa +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +import gtk +import gtk.glade +import math +import cairo + +_ = lambda x:x + +class Ball(object): + DEFAULT_WIDTH = 10 + + def __init__(self, x, y, r, name='', position=0): + self.position = position + self.x = x + self.y = y + self.radios = r + self.name = name + +class BallManager(list): + def __init__(self, *args, **kwargs): + super(BallManager, self).__init__(*args, **kwargs) + + def save_to_file(self, path): + target = open(path, 'w') + for i in self: + target.write('%d,%d %d %s\n' % (i.x, i.y, i.radios, i.name)) + target.close() + +class GladeLoader(object): + def __init__(self, fname, root=''): + self.ui = gtk.glade.XML(fname, root) + self.ui.signal_autoconnect(self) + + def get_widget(self, wname): + return self.ui.get_widget(wname) + + # little shortcut + wid = get_widget + + # glade callbacks + + def gtk_widget_show(self, widget, *args): + widget.show() + return True + + def gtk_widget_hide(self, widget, *args): + widget.hide() + return True + + def gtk_main_quit(self, *args): + gtk.main_quit() + + def gtk_main(self, *args): + gtk.main() + +class NewProject(GladeLoader): + def __init__(self, parent=None): + super(NewProject, self).__init__('gzv.glade', 'new-project') + if parent: + self.wid('new-project').set_transient_for(parent) + +class Gzv(GladeLoader): + def __init__(self): + super(Gzv, self).__init__('gzv.glade', 'main-window') + self.window = self.wid('main-window') + self.window.connect('delete-event', lambda *x: gtk.main_quit()) + + self.evtbox = self.wid('eventbox') + self.evtbox.connect('button-press-event', self.button_press) + self.evtbox.connect('button-release-event', self.button_release) + self.evtbox.connect('motion-notify-event', self.motion_notify) + + self.model = gtk.ListStore(int, str) + self.treeview = self.wid('treeview') + self.treeview.set_model(self.model) + + self.draw = self.wid('draw') + self.draw.connect('expose-event', self.expose_draw) + + # FIXME: Hardcoded. + self.image = 'skol.jpg' + self.balls = self.load_balls_from_file('xxx') + self.load_balls_to_treeview() + + # this *MUST* be called *AFTER* load_balls_to_treeview + self.setup_treeview() + + self.ball_width = Ball.DEFAULT_WIDTH + self.selecting = False + self.start_x = -1 + self.start_y = -1 + + def setup_treeview(self): + self.model.connect('rows-reordered', self.on_rows_reordered) + + renderer = gtk.CellRendererText() + column = gtk.TreeViewColumn(_('Position'), renderer, text=0) + self.treeview.append_column(column) + + renderer = gtk.CellRendererText() + renderer.connect('edited', self.on_cell_edited) + renderer.set_property('editable', True) + column = gtk.TreeViewColumn(_('Name'), renderer, text=1) + self.treeview.append_column(column) + + def on_rows_reordered(self, *args): + print + + def on_cell_edited(self, *args): + print args + + def new_project(self, button): + dialog = NewProject(self.window).wid('new-project') + + # This '1' was defined in the glade file + if dialog.run() == 1: + pass + dialog.destroy() + + def open_file_chooser(self, button): + fc = gtk.FileChooserDialog(_('Choose an image'), self, + buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, + gtk.STOCK_OK, gtk.RESPONSE_OK)) + if fc.run() == gtk.RESPONSE_OK: + self.image = fc.get_filename() + + fc.destroy() + + def load_balls_to_treeview(self): + model = self.treeview.get_model() + for i in self.balls: + model.append([i.position, i.name]) + + def load_balls_from_file(self, fname): + balls = BallManager() + for index, line in enumerate(file(fname)): + if not line: + continue + pos, radios, name = line.split() + x, y = pos.split(',') + balls.append(Ball(int(x), int(y), int(radios), name, index)) + return balls + + def expose_draw(self, draw, event): + if not self.image: + return + + # loading the picture image and getting some useful + # information to draw it in the widget's background + img = gtk.gdk.pixbuf_new_from_file(self.image) + pixels = img.get_pixels() + rowstride = img.get_rowstride() + width = img.get_width() + height = img.get_height() + gc = draw.style.black_gc + + # sets the correct size of the eventbox, to show the scrollbar + # when needed. + self.evtbox.set_size_request(width, height) + + # drawing the picture in the background of the drawing area, + # this is really important. + draw.window.draw_rgb_image(gc, 0, 0, width, height, + 'normal', pixels, rowstride, + 0, 0) + + # this call makes the ball being drown be shown correctly. + self.draw_current_ball() + + # drawing other balls stored in the self.balls list. + ctx = draw.window.cairo_create() + ctx.fill() + + ctx.set_line_width(10.0) + ctx.set_source_rgba (0.5, 0.0, 0.0, 0.4) + + for i in self.balls: + ctx.arc(i.x, i.y, i.radios, 0, 64*math.pi) + + ctx.fill() + ctx.stroke() + + def draw_current_ball(self): + if self.start_x < 0: + return + ctx = self.draw.window.cairo_create() + ctx.arc(self.start_x, self.start_y, self.ball_width, 0, 64*math.pi) + ctx.set_source_rgba (0.5, 0.0, 0.0, 0.4) + ctx.fill() + + def button_press(self, widget, event): + if event.button == 1: + self.selecting = True + self.start_x = event.x + self.start_y = event.y + self.last_x = event.x + + def button_release(self, widget, event): + if event.button == 1: + self.selecting = False + self.finish_drawing() + + def motion_notify(self, widget, event): + self.draw.queue_draw() + + if event.x > self.last_x: + self.ball_width += 2 + else: + self.ball_width -= 2 + + self.last_x = event.x + + def finish_drawing(self): + self.draw_current_ball() + self.ball_width = Ball.DEFAULT_WIDTH + +if __name__ == '__main__': + Gzv().window.show_all() + gtk.main() -- 2.20.1