├── .gitignore ├── Help_page.html ├── IPython-powered_Slideshow_Reveal-ed.ipynb ├── README.rst └── viper.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc -------------------------------------------------------------------------------- /Help_page.html: -------------------------------------------------------------------------------- 1 | 2 | 24 | 25 | 26 | 27 | 28 | vIPer 29 | 30 | 41 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 |
58 |
59 |
60 | 61 | 62 |
63 |
64 |
65 | 66 |

vIPer

67 | 68 | 69 |

vIPer is an application specifically designed to work with IPython notebooks.

70 |

With vIPer you can:

71 | 80 | 81 | 82 |

How to use it?

83 |

$ipython notebook --pylab=inline --no-browser

84 |

(pylab is optional)

85 |

Note: Set the --notebook-dir as the vIPer directory or open ipython notebook in the same directory where vIPer and the notebooks you are working lives.

86 |

$python viper.py

87 |

(Dashboard page use port 8888, but you want to use another port, point to it using the address bar: Ctrl+a)

88 | 89 | 90 |

Dependencies

91 |

Ver: 0.13 (this is a very alpha release, you can play with it... but surely you will found several bugs.

92 |
    93 |
  • Python 2.7
  • 94 |
  • IPython > 0.12 (with its own dependencies)
  • 95 |
  • PyQt > 4.9
  • 96 |
  • Jinja2
  • 97 |
  • Pygments
  • 98 |
  • Latest Docutils
  • 99 |
100 |

For example:

101 |

curl http://docutils.svn.sourceforge.net/viewvc/docutils/trunk/docutils/?view=tar > docutils.gz

102 |

pip install -U docutils.gz

103 |
    104 |
  • RecordMyDesktop (if you want to record audio and video).
  • 105 |
106 | 107 | 108 |

Slide mode

109 |

Use ---- in a Markdown cell to separate each slide in the working notebook.

110 |

The title of each slide is the first cell after ----, except in the first one (you do not have to begin with the notebook with ---- ).

111 |

All you write under "Presenter notes" will be only rendered in the presenter mode of slideshow view (pressing "p").

112 |

In the slide view, press "h" for more help.

113 | 114 | 115 |

Shorcuts

116 |

NewTab: Ctrl+t

117 |

CloseTab: Ctrl+w

118 |

AdressBar: Ctrl+a

119 |

Go2AdrBar: Ctrl+l

120 |

DelAdrBar: Ctrl+d

121 |

Cut: Ctrl+x

122 |

Copy: Ctrl+c

123 |

Paste: Ctrl+v

124 |

Undo: Ctrl+z

125 |

Redo: Ctrl+y

126 |

SaveNoteb: Ctrl+s or Ctrl+m+s

127 |

Search: Ctrl+f

128 |

Zoom: Ctrl++/+-/+0

129 |

Print: Ctrl+p

130 |

Htmler: Ctrl+j

131 |

Slider: Ctrl+k

132 |

Record: Ctrl+r

133 |

StopRec: Ctrl+Alt+s

134 |

SplitVert: F9

135 |

SplitHori: F10

136 |

SplitTog: F11

137 |

FullScr: F12

138 |

Help: Ctrl+h

139 |

Quit: Ctrl+q

140 | 141 | 142 |

Author: Damián Avila, Jul 2012

143 | 144 |
145 |
146 |
147 | 148 |
149 |
150 | 151 | 152 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | vIPer 2 | ===== 3 | 4 | vIPer is an application specifically designed to work with IPython notebooks. 5 | 6 | With vIPer you can: 7 | 8 | * Write and execute IPython notebooks. 9 | * Generate static html views of IPython notebooks. 10 | * Generate slideshow views of IPython notebooks. 11 | * Split your screen to see how your changes in the current notebook are rendered in html and slideshow views. 12 | * Record audio and video from your IPython sessions. 13 | * Surf the web. 14 | * And more is comming... 15 | 16 | How to use it? 17 | ============== 18 | 19 | \$ipython notebook --no-browser 20 | 21 | *Note: Set the --notebook-dir as the vIPer directory or open ipython notebook in the same directory where vIPer and the notebooks you are working lives.* 22 | 23 | \$python viper.py 24 | 25 | (Dashboard page use port 8888, but you want to use another port, point to it using the address bar: Ctrl+a) 26 | 27 | Dependencies 28 | ============ 29 | 30 | **Version 1.1**: now supporting the last IPython release and using the IPython.nbconvert machinery (as usual... you can play with it, but surely you will find several bugs, just report them). 31 | 32 | * Python 2.7 (obviously...) 33 | * IPython > 1.1.0 (with its own dependencies) 34 | * PyQt > 4.9 35 | * Jinja2 36 | * Pygments 37 | * RecordMyDesktop (if you want to record audio and video). 38 | 39 | License 40 | ======= 41 | 42 | BSD-Modified (also known as New BSD or Revisited BSD). 43 | 44 | Author: Damián Avila 45 | ==================== 46 | 47 | -------------------------------------------------------------------------------- /viper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | #ver_1.1 4 | 5 | import sys 6 | import subprocess 7 | import os 8 | import signal 9 | from PyQt4 import QtGui, QtCore, QtWebKit 10 | from IPython.nbformat import current as nbformat 11 | from IPython.nbconvert.exporters import HTMLExporter, SlidesExporter 12 | from IPython.nbconvert.post_processors.serve import ServePostProcessor 13 | 14 | 15 | class MainWindow(QtGui.QMainWindow): 16 | def __init__(self): 17 | QtGui.QMainWindow.__init__(self) 18 | self.tabs = QtGui.QTabWidget(self, 19 | tabsClosable=True, 20 | movable=True, 21 | elideMode=QtCore.Qt.ElideRight, 22 | currentChanged=self.currentTabChanged, 23 | tabCloseRequested=self.closeTabRequested) 24 | self.bottom = QtWebKit.QWebView(self) 25 | self.setWindowTitle("vIPer") 26 | self.splitter = QtGui.QSplitter(self) 27 | self.setCentralWidget(self.splitter) 28 | self.splitter.addWidget(self.tabs) 29 | self.splitter.addWidget(self.bottom) 30 | self.bottom.setVisible(False) 31 | self.tabWidgets = [] 32 | self.titleHistory = [] 33 | self.path = QtCore.QDir.currentPath() 34 | self.newtab = QtGui.QAction(QtGui.QIcon.fromTheme("document-new"), 35 | "New Tab", 36 | self, 37 | triggered=self.newTabTriggered, 38 | shortcut="Ctrl+t") 39 | self.htmler = QtGui.QAction(QtGui.QIcon.fromTheme("go-down"), 40 | "Htmler", 41 | self, 42 | triggered=self.screenHtmled, 43 | shortcut="Ctrl+j") 44 | self.slider = QtGui.QAction(QtGui.QIcon.fromTheme("go-jump"), 45 | "Slider", 46 | self, 47 | triggered=self.screenSlided, 48 | shortcut="Ctrl+k") 49 | self.splitterV = QtGui.QAction(QtGui.QIcon.fromTheme( 50 | "object-flip-vertical"), 51 | "Split vertically", 52 | self, 53 | triggered=self.screenSplittedVhtml, 54 | shortcut="F9") 55 | self.splitterV.setMenu(QtGui.QMenu()) 56 | self.splitterV.menu().addAction(QtGui.QAction('HTML', 57 | self, 58 | triggered=self.screenSplittedVhtml)) 59 | self.splitterV.menu().addAction(QtGui.QAction('SLIDE', 60 | self, 61 | triggered=self.screenSplittedVslide)) 62 | self.splitterH = QtGui.QAction(QtGui.QIcon.fromTheme( 63 | "object-flip-horizontal"), 64 | "Split horizontally", 65 | self, 66 | triggered=self.screenSplittedHhtml, 67 | shortcut="F10") 68 | self.splitterH.setMenu(QtGui.QMenu()) 69 | self.splitterH.menu().addAction(QtGui.QAction('HTML', 70 | self, 71 | triggered=self.screenSplittedHhtml)) 72 | self.splitterH.menu().addAction(QtGui.QAction('SLIDE', 73 | self, 74 | triggered=self.screenSplittedHslide)) 75 | self.recorder = QtGui.QAction(QtGui.QIcon.fromTheme( 76 | "media-playback-start"), 77 | "Record", 78 | self, 79 | triggered=self.screenRecorded, 80 | shortcut="Ctrl+r") 81 | self.stopper = QtGui.QAction(QtGui.QIcon.fromTheme( 82 | "media-playback-stop"), 83 | "Stop", 84 | self, 85 | triggered=self.screenStopped, 86 | shortcut="Ctrl+Alt+s") 87 | self.addAction(QtGui.QAction("Split Screen", 88 | self, 89 | checkable=True, 90 | toggled=self.splitToggled, 91 | shortcut="F11")) 92 | self.addAction(QtGui.QAction("Full Screen", 93 | self, 94 | checkable=True, 95 | toggled=self.screenToggled, 96 | shortcut="F12")) 97 | self.helper = QtGui.QAction(QtGui.QIcon.fromTheme("help-faq"), 98 | "Help", 99 | self, 100 | triggered=self.newHelpTabTriggered, 101 | shortcut="Ctrl+h") 102 | self.full = HTMLExporter() 103 | self.html = '.html' 104 | self.rev = SlidesExporter() 105 | self.rev_html = '.slides.html' 106 | self.server = ServePostProcessor() 107 | self.servePool = [] 108 | self.horizontal = QtCore.Qt.Horizontal 109 | self.vertical = QtCore.Qt.Vertical 110 | self.addTab(QtCore.QUrl('http://127.0.0.1:8888/')) 111 | 112 | def addTab(self, url=QtCore.QUrl("")): 113 | self.tabs.setCurrentIndex(self.tabs.addTab(Tab(url, self), "")) 114 | return self.tabs.currentWidget() 115 | 116 | def newTabTriggered(self): 117 | self.addTab(QtCore.QUrl('http://ipython.org/')) 118 | l = 'If you want to surf the web, get an address bar pressing Ctrl + A' 119 | self.statusBar().showMessage(l, 5000) 120 | 121 | def newHelpTabTriggered(self): 122 | localH = 'Help_page' + '.html' 123 | self.addTab(QtCore.QUrl.fromLocalFile(self.path + '/' + localH)) 124 | 125 | def currentTabChanged(self, idx): 126 | wb = self.tabs.widget(idx) 127 | self.addToTitleHistory(unicode(wb.title())) 128 | if wb is None: 129 | return self.close() 130 | for w in self.tabWidgets: 131 | w.hide() 132 | self.tabWidgets = [wb.tb, wb.pbar, wb.lineUrl, wb.search] 133 | self.addToolBar(wb.tb) 134 | for w in self.tabWidgets[:-3]: 135 | w.show() 136 | 137 | def addToTitleHistory(self, title): 138 | self.titleHistory.append(title) 139 | 140 | def closeTabRequested(self, idx): 141 | self.tabs.widget(idx).deleteLater() 142 | 143 | def nbConverter(self, exporter, extension): 144 | self.infile = self.titleHistory[-1] + '.ipynb' 145 | self.notebook = open(self.infile).read() 146 | self.nb_json = nbformat.reads_json(self.notebook) 147 | self.exportHtml = exporter 148 | (body, resources) = self.exportHtml.from_notebook_node(self.nb_json) 149 | self.outfile = self.titleHistory[-1] + extension 150 | open(self.outfile, 'w').write(body.encode('utf-8')) 151 | 152 | def screenHtmled(self): 153 | self.screenOS = ScreenMainer(self.full, self.html, self) 154 | 155 | def screenSlided(self): 156 | self.screenOS = ScreenMainer(self.rev, self.rev_html, self) 157 | 158 | def screenSplittedVhtml(self): 159 | self.screenV = ScreenSplitter(self.vertical, 1.0, 160 | self.full, self.html, self) 161 | 162 | def screenSplittedHhtml(self): 163 | self.screenH = ScreenSplitter(self.horizontal, 1.0, 164 | self.full, self.html, self) 165 | 166 | def screenSplittedVslide(self): 167 | self.screenV = ScreenSplitter(self.vertical, 1.0, 168 | self.rev, self.rev_html, self) 169 | 170 | def screenSplittedHslide(self): 171 | self.screenH = ScreenSplitter(self.horizontal, 1.0, 172 | self.rev, self.rev_html, self) 173 | 174 | def screenRecorded(self): 175 | l = 'Recording audio and video...' 176 | self.statusBar().showMessage(l, 5000) 177 | self.cmd = 'recordmydesktop -o ' + self.titleHistory[-1] + '.ogv' 178 | self.recordMachinary = subprocess.Popen(self.cmd, 179 | stdout=subprocess.PIPE, 180 | # stderr=subprocess.PIPE, 181 | shell=True, 182 | preexec_fn=os.setsid) 183 | # html, stderr = proc.communicate() 184 | # if stderr: 185 | # raise IOError(stderr) 186 | 187 | def screenStopped(self): 188 | l = 'Stopping audio and video recording...' 189 | self.statusBar().showMessage(l, 3000) 190 | os.killpg(self.recordMachinary.pid, signal.SIGTERM) 191 | 192 | def splitToggled(self, v): 193 | self.bottom.setVisible(False) if v else self.bottom.setVisible(True) 194 | 195 | def screenToggled(self, v): 196 | self.showFullScreen() if v else self.showNormal() 197 | 198 | 199 | class Converter: 200 | def __init__(self, exporter, extension, container): 201 | self.container = container 202 | self.extension = extension 203 | self.exporter = exporter 204 | 205 | self.container.nbConverter(self.exporter, self.extension) 206 | self.container.servePool.append(ServeThread(self.extension, 207 | self.container)) 208 | if len(self.container.servePool) == 1: 209 | self.container.servePool[0].start() 210 | else: 211 | pass 212 | 213 | 214 | class ServeThread(QtCore.QThread): 215 | def __init__(self, extension, container): 216 | QtCore.QThread.__init__(self) 217 | self.container = container 218 | self.extension = extension 219 | 220 | def run(self): 221 | localO = self.container.titleHistory[-1] + self.extension 222 | self.container.server.open_in_browser = False 223 | self.container.server(str(self.container.path) + '/' + localO) 224 | 225 | 226 | class ScreenMainer: 227 | def __init__(self, exporter, extension, container): 228 | self.container = container 229 | self.extension = extension 230 | self.exporter = exporter 231 | 232 | #try: 233 | l = 'Building a view from the IPython notebook, please wait...' 234 | self.container.statusBar().showMessage(l, 3000) 235 | self.container.screenOS = Converter(self.exporter, 236 | self.extension, 237 | self.container) 238 | self.container.addTab(QtCore.QUrl('http://127.0.0.1:8000/' + 239 | self.container.titleHistory[-1] + 240 | self.extension)) 241 | #except IOError: 242 | #l = 'This tab is not an IPython notebook' 243 | #self.statusBar().showMessage(l, 3000) 244 | 245 | 246 | class ScreenSplitter: 247 | def __init__(self, orientation, zoom, exporter, extension, container): 248 | self.container = container 249 | self.extension = extension 250 | self.exporter = exporter 251 | self.zoom = zoom 252 | self.orientation = orientation 253 | 254 | self.container.splitter.setOrientation(self.orientation) 255 | #try: 256 | l = 'Building the splitted view, please wait...' 257 | self.container.statusBar().showMessage(l, 3000) 258 | self.container.screenHtmler = Converter(exporter, self.extension, 259 | self.container) 260 | self.container.bottom.load(QtCore.QUrl('http://127.0.0.1:8000/' + 261 | self.container.titleHistory[-1] + 262 | self.extension)) 263 | self.container.bottom.setVisible(True) 264 | self.container.bottom.setZoomFactor(self.zoom) 265 | #except IOError: 266 | #l = 'This tab is not an IPython notebook' 267 | #self.container.statusBar().showMessage(l, 3000) 268 | 269 | 270 | class Tab(QtWebKit.QWebView): 271 | def __init__(self, url, container): 272 | self.container = container 273 | self.pbar = QtGui.QProgressBar(maximumWidth=120, visible=False) 274 | self.tab = QtWebKit.QWebView.__init__(self, 275 | loadProgress=lambda v: (self.pbar.show(), self.pbar.setValue(v)), 276 | loadFinished=self.pbar.hide, 277 | loadStarted=lambda: self.pbar.show(), 278 | titleChanged=self.titleTabChanged) 279 | 280 | self.tb = QtGui.QToolBar("Main Toolbar") 281 | for a, sc in [[QtWebKit.QWebPage.Back, "Alt+Left"], 282 | [QtWebKit.QWebPage.Forward, "Alt+Right"], 283 | [QtWebKit.QWebPage.Reload, "Ctrl+r"] 284 | ]: 285 | self.tb.addAction(self.pageAction(a)) 286 | self.pageAction(a).setShortcut(sc) 287 | self.tb.addAction(container.newtab) 288 | self.tb.addAction(container.htmler) 289 | self.tb.addAction(container.slider) 290 | self.tb.addAction(container.splitterV) 291 | self.tb.addAction(container.splitterH) 292 | self.tb.addAction(container.recorder) 293 | self.tb.addAction(container.stopper) 294 | self.tb.addAction(container.helper) 295 | 296 | self.titleChanged.connect(lambda t: 297 | container.addToTitleHistory(unicode(t))) 298 | 299 | self.lineUrl = QtGui.QLineEdit(visible=False, 300 | returnPressed=lambda: 301 | self.setUrl(QtCore.QUrl.fromUserInput(self.lineUrl.text()))) 302 | self.showAddress = QtGui.QShortcut("Ctrl+a", 303 | self, 304 | activated=lambda: 305 | self.lineUrl.show() or self.lineUrl.setFocus()) 306 | self.hideAddress = QtGui.QShortcut("Ctrl+d", 307 | self, 308 | activated=lambda: 309 | (self.lineUrl.hide(), self.setFocus())) 310 | 311 | self.search = QtGui.QLineEdit(visible=False, 312 | returnPressed=lambda: 313 | self.findText(self.search.text())) 314 | self.addAction(QtGui.QAction("Search", 315 | self, 316 | checkable=True, 317 | toggled=self.searchToggled, 318 | shortcut="Ctrl+f")) 319 | 320 | self.do_close = QtGui.QShortcut("Ctrl+w", 321 | self, 322 | activated=lambda: 323 | container.tabs.removeTab(container.tabs.indexOf(self))) 324 | self.do_quit = QtGui.QShortcut("Ctrl+q", 325 | self, 326 | activated=lambda: container.close()) 327 | 328 | self.zoomIn = QtGui.QShortcut("Ctrl++", 329 | self, 330 | activated=lambda: 331 | self.setZoomFactor(self.zoomFactor() + 0.2)) 332 | self.zoomOut = QtGui.QShortcut("Ctrl+-", 333 | self, 334 | activated=lambda: 335 | self.setZoomFactor(self.zoomFactor() - 0.2)) 336 | self.zoomOne = QtGui.QShortcut("Ctrl+0", 337 | self, 338 | activated=lambda: 339 | self.setZoomFactor(1)) 340 | 341 | self.urlFocus = QtGui.QShortcut("Ctrl+l", 342 | self, 343 | activated=self.lineUrl.setFocus) 344 | 345 | self.printLater() 346 | 347 | container.statusBar().addPermanentWidget(self.pbar) 348 | container.statusBar().addPermanentWidget(self.lineUrl) 349 | container.statusBar().addPermanentWidget(self.search) 350 | 351 | self.settings().setAttribute(QtWebKit.QWebSettings.PluginsEnabled, 352 | True) 353 | self.load(url) 354 | 355 | def titleTabChanged(self, t): 356 | if self.amCurrent(): 357 | self.container.tabs.setTabText( 358 | self.container.tabs.indexOf(self), t) 359 | 360 | def lineUrlToggled(self, v): 361 | if v is True: 362 | self.lineUrl.show() or self.lineUrl.setFocus() 363 | else: 364 | (self.lineUrl.hide(), self.setFocus()) 365 | 366 | def searchToggled(self, v): 367 | if v is True: 368 | self.search.show() or self.search.setFocus() 369 | else: 370 | (self.search.hide(), self.setFocus()) 371 | 372 | def printLater(self): 373 | self.previewer = QtGui.QPrintPreviewDialog(paintRequested=self.print_) 374 | self.do_print = self.tb.addAction(QtGui.QAction( 375 | QtGui.QIcon.fromTheme("document-print"), 376 | "Print", 377 | self, 378 | triggered=self.previewer.exec_, 379 | shortcut="Ctrl+p")) 380 | 381 | amCurrent = lambda self: self.container.tabs.currentWidget() == self 382 | 383 | def createWindow(self, windowType): 384 | return self.container.addTab() 385 | 386 | if __name__ == "__main__": 387 | app = QtGui.QApplication(sys.argv) 388 | wb = MainWindow() 389 | wb.showMaximized() 390 | sys.exit(app.exec_()) 391 | --------------------------------------------------------------------------------