wch / webshot

Take screenshots of web pages from R
http://wch.github.io/webshot/
228 stars 40 forks source link

use website 'inside' a shiny app? #20

Open vnijs opened 8 years ago

vnijs commented 8 years ago

Is it feasible to run webshot from a shiny app? Ideally, I'd like to press a button to take a screenshot of the current page in a shiny app, store the image, and then put a reference to the screenshot into an knitr document in the app or into Rstudio. I tried a few things it but it locked up the session but I want the app to continue working after the screenshot is taken. I think I could get this (mostly) working assuming webshot can be called from a shiny app.

Alternatively, could an Rstudio addin be used to create a screen-shot of the active app and put the result into the Rstudio Viewer? As far as I can tell, you can only run one addin at a time unfortunately.

yihui commented 8 years ago

You should not call webshot::appshot() to screenshot the app, since that will lauch an app in another R session. Instead, just pass the URL of the running app to webshot::webshot(). Then the question is how to access the URL of the current app in R, and I don't know if there is a straightforward answer...

vnijs commented 8 years ago

@yihui I did try that. Passing a url to an external site (e.g., google) works fine. In a shiny app you can use clientData to find out the app url. Works fine. However, when you pass it to webshot the shiny app locks up. See reproducible example below.

server <- function(session, input, output) {
  output$distPlot <- renderPlot({
    hist(rnorm(input$obs), col = 'darkgray', border = 'white')
  })

  observeEvent(input$screenshot, {
    cdat <- session$clientData
    url <- paste0(cdat$url_protocol,"//",cdat$url_hostname,":", cdat$url_port, cdat$url_pathname,cdat$url_search)
    print(url)
    # url <- "http://www.google.com/"
    webshot::webshot(url, "~/Desktop/webshot.png")
  })
}

ui <- fluidPage(
  sidebarLayout(
    sidebarPanel(
      actionButton("screenshot","Take a screenshot"), br(),
      sliderInput("obs", "Number of observations:", min = 10, max = 500, value = 100)
    ),
    mainPanel(plotOutput("distPlot"))
  )
)

shinyApp(ui = ui, server = server)
yihui commented 8 years ago

Okay, I have no idea about how it could lock up the app then...

wch commented 8 years ago

Hm, I'm able to run the app and then run the webshot.js script from the command line to get a screenshot. For example:

/home/winston/R/3.2/webshot/webshot.js http://127.0.0.1:7714/ webshot.png --vwidth=992 --vheight=744 --delay=0.2

This is the exact same command line that webshot runs.

However, if I try to call webshot() from inside the app, it hangs. I wonder if the problem is that the child process also inherits the open port (7714), and it blocks as a result of that somehow. Here's something in that direction: http://stackoverflow.com/a/9133957

That said, I don't think this will do what you want, @vnijs. It would take a screenshot of a new connection to the same app, not a screenshot of your existing session. To do the latter, I think you'd need a browser extension or a regular screenshot program.

wch commented 8 years ago

I got an email from Uwe Ligges about a related issue that appears to be Windows-specific.

package webshot hangs on winbuilder from time to time in a way, that I cannot even kill it appropriately.

It hangs when rebuilding vignettes and the log file from that is:

Quitting from lines 96-98 (intro.Rmd) Error: processing vignette 'intro.Rmd' failed with diagnostics: cannot open the connection Execution halted Loading required package: shiny

Listening on http://127.0.0.1:7845

p0bs commented 5 years ago

Has anything on this issue changed recently, with the advent of appshot?

Specifically, is it now possible to take a shot of the screen with the current state of a shiny app (i.e. once all user inputs have been made)? I ask because I'm trying to find a good way to save outputs generically as image files and this looks like a good bet.

Many thanks to you all.

vnijs commented 5 years ago

I don't think so no. I believe you can take screenshots with shinytest however.

https://github.com/rstudio/shinytest

ManuHamel commented 5 years ago

I had the same problem on Windows! If we create the subprocess that calls phantomjs with the callr package , there is a "bug" in the phantom_run function when you call the function webshot in a shinyApp on the shinyApp that contains the webshot call.

However, if you use the function spawn_process in the R package subprocess to create a subprocess that calls phantomjs, then there is no bug.

In sum, we simply have to modify the phantom_run function to make it work by using the R package subprocess instead of callr.

leungi commented 5 years ago

Test @vnijs sample app above on Linux; same result.

ManuHamel commented 5 years ago

If you use the spawn_process function instead of the callr R package in the function phantom_run function for the sample app that is given in this page, it works on Windows (at least on my computer).

vnijs commented 5 years ago

That sounds very interesting @ManuHamel. Could you post a reproducible example?

ManuHamel commented 5 years ago

Hi,

Here is an example that is working on my computer on Windows : https://github.com/ManuHamel/callWebshotInShinyapp Also, some functions in the package are from the webshot R package. I do not claim to be the author.

Best regards,

Emmanuel Hamel

leungi commented 5 years ago

Tested and working locally, but can't get in working on Shiny server 😕

Checked logs and can't seem to find out what's wrong - phantomjs found, saving to a writable folder by shiny user.

Kudos and thanks to @ManuHamel 🙇

ManuHamel commented 5 years ago

When I will have time, I will test it on shiny server and I will let you know If I find a solution.

leungi commented 5 years ago

Thanks @ManuHamel.

In case this helps:

In Linux (no error reported in Shiny logs, but no output file):

url = "http://www.google.com"
path_to_file <- '"/home/shiny/webshot-js.png"'
lien_vers_package <- system.file(package = "webshot")
path_to_webshot_js <- '"/usr/lib64/R/library/webshot/webshot_modif.js"'
arg1 <- paste0("[{\'url\':\'", url, "\'", ",\'file\':\'", path_to_file, "\'}]")
path_To_Phantom <- "/usr/lib64/R/library/webshot/PhantomJS/phantomjs"
args <- c(path_to_webshot_js, arg1)
subprocess::spawn_process(command = path_To_Phantom, arguments = args)

# resulting subprocess call
# /usr/lib64/R/library/webshot/PhantomJS/phantomjs-2.1.1-linux-x86_64/bin/phantomjs "/usr/lib64/R/library/webshot/webshot_modif.js" [{'url':'http://www.google.com','file':'"/home/shiny/webshot-js.png"'}]

In Windows (works as designed):

url = "http://www.google.com"
path_to_file <- '"./callWebshotInShinyapp/Save Screenshot PNG/webshot5.png"'
lien_vers_package <- system.file(package = "webshot")
path_to_webshot_js <- '"C:/Users/Public/Data/R/R-3.5.1/library/webshot/webshot_modif.js"'
arg1 <- paste0("[{\'url\':\'", url, "\'", ",\'file\':\'", path_to_file, "\'}]")
path_To_Phantom <- find_phantom()
args <- c(path_to_webshot_js, arg1)
subprocess::spawn_process(command = path_To_Phantom, arguments = args)

# resulting subprocess call
# C:\Users\leungi\AppData\Roaming\PhantomJS\phantomjs.exe "C:/Users/Public/Data/R/R-3.5.1/library/webshot/webshot_modif.js" [{'url':'http://www.google.com','file':'"./callWebshotInShinyapp/callWebshotInShinyapp/Save Screenshot PNG/webshot3.png"'}]
wch commented 5 years ago

@vnijs With https://github.com/rstudio/chromote, you should be able to achieve your original goal. The idea is that you launch headless chrome, then open a viewer into that headless browser, interact via the viewer, and then take a screenshot.

You'd do something like this:

library(shiny)
runExample("01_hello", port = 1234)

Then in another R session:

library(chromote)
b <- ChromoteSession$new()
b$view() # Opens a viewer to the headless browser, in a regular browser window
b$Page$navigate("http://localhost:1234")

Now you can interact with the app via the viewer. Once you're done interacting, you can do:

b$screenshot("1.png")
leungi commented 5 years ago

@wch, I presume this is similar to your webshot2.

If I understand it correctly, to get a screenshot of a particular user's active session (with all the input/output), we'll need to bookmark the session for this solution to work. This is because we won't know ahead of time how the particular user will interact with app.

Screen-shooting a fresh app will work with this solution, of course.

wch commented 5 years ago

@leungi It's not possible to get a screenshot of any arbitrary user's active session. What I was describing was how to get a screenshot of your own active session.

leungi commented 5 years ago

@wch: as I suspected, even after relaunching a fresh headless browser after each update of user input in Shiny app, the captured image is always the original.

vnijs commented 4 years ago

FYI snapper (https://github.com/yonicd/snapper) does pretty much exactly what I was looking for, i.e., create a screenshot of a shiny app while you are using it). There are, unfortunately some important edge cases (e.g., https://github.com/yonicd/snapper/issues/4) that don't work yet. This particular problem stems from an issue in html2canvas (https://github.com/niklasvh/html2canvas/issues/2166).