Automatyczne łączenie prezentacji w jedną

Użytkowanie edytora prezentacji
Awatar użytkownika
heillos
Posty: 164
Rejestracja: wt cze 02, 2009 8:19 am

Automatyczne łączenie prezentacji w jedną

Post autor: heillos »

Potrzebuję łączyć (często, więc automatycznie) wiele prezentacji (PPT, ale ostatecznie może być ODP) w jedno. Najlepsze byłoby jakieś konsolowe narzędzie nie wymagające X-ów, ale ostatecznie rozwiązanie z wykorzystaniem LibreOffice też ujdzie, jak się go będzie dało jakoś wołać ze skryptu. Czy spotkaliście się z takim narzędziem? Albo wiecie jak to Librem zautomatyzować?
Jan_J
Posty: 4560
Rejestracja: pt maja 22, 2009 1:20 pm
Lokalizacja: Wrocław

Re: Automatyczne łączenie prezentacji w jedną

Post autor: Jan_J »

Na poziomie łączenia ciągów slajdów w formacie pdf możesz skorzystać z pdftk.
Dla dokumentów odp/ppt da się bez większych trudności napisać coś w rodzaju:
for slajd in prezentacja2 { dołącz slajd do prezentacja1 }
ale powstanie masa pytań: co z odsyłaczami, nazwami stylów, dziedziczeniem stylów, pełną "multimedią" itp.
JJ
LO (7.6|24.2) ∙ Python (3.12|3.10) ∙ Unicode 15 ∙ LᴬTEX 2ε ∙ XML ∙ Unix tools ∙ Linux (Rocky|CentOS)
Awatar użytkownika
heillos
Posty: 164
Rejestracja: wt cze 02, 2009 8:19 am

Re: Automatyczne łączenie prezentacji w jedną

Post autor: heillos »

Jan_J pisze:Na poziomie łączenia ciągów slajdów w formacie pdf możesz skorzystać z pdftk.
Dla dokumentów odp/ppt da się bez większych trudności napisać coś w rodzaju:
for slajd in prezentacja2 { dołącz slajd do prezentacja1 }
ale powstanie masa pytań: co z odsyłaczami, nazwami stylów, dziedziczeniem stylów, pełną "multimedią" itp.
Nie ma odsyłaczy, to są proste slajdy z tekstami (głównie pieśni) i chodzi o komponowanie jednej prezentacji z gotowych cegiełek-pieśni. Nie może to być PDF, bo ostatecznie musi powstać PPT, a PDF nie przerobię do PPT. Stylów też nie używamy, bo style w prezentacjach to dno niestety :( I psińco się da tym zrobić, jakby ich nie było w ogóle :(

Odnośnie tego fora, to właśnie nie bardzo wiem, jak to oskryptować... Stąd moje pytanie. Jakoś wołać wsadowo LO (choć to o tyle marne, że wymaga Xów)? Bo jak rozumiem, zewnętrznego programiku pewnie nie znajdę, co to robi?
Jan_J
Posty: 4560
Rejestracja: pt maja 22, 2009 1:20 pm
Lokalizacja: Wrocław

Re: Automatyczne łączenie prezentacji w jedną

Post autor: Jan_J »

Dobra, spróbuję w wolnej chwili szkicowy skrypt wysmażyć.
Jeżeli OO uruchomić w trybie serwera (--headless), to grafika nie będzie potrzebna.
JJ
LO (7.6|24.2) ∙ Python (3.12|3.10) ∙ Unicode 15 ∙ LᴬTEX 2ε ∙ XML ∙ Unix tools ∙ Linux (Rocky|CentOS)
Awatar użytkownika
heillos
Posty: 164
Rejestracja: wt cze 02, 2009 8:19 am

Re: Automatyczne łączenie prezentacji w jedną

Post autor: heillos »

Mówisz? Myśmy mieli kiedyś w szkole jakiś Dziennik Lekcyjny, co używał OOo do generowania papierków i kazali jakieś dump-Xy instalować...
Jan_J
Posty: 4560
Rejestracja: pt maja 22, 2009 1:20 pm
Lokalizacja: Wrocław

Re: Automatyczne łączenie prezentacji w jedną

Post autor: Jan_J »

Jeżeli uruchamiasz OOo w trybie użytkownika i w nim zapuszczasz makropolecenie, to oczywiście środowisko graficzne jest potrzebne. Ale jeżeli działa w trybie serwera, z opcją --headless, to radzi sobie bez niego. Wtedy nie uruchamia się makr, tylko programy klienckie.

Pierwszy szkic ma postać makra w Basicu. Jest więc stosunkowo łatwy do przetestowania. Ale wymaga GUI, chyba że różnymi trickami uruchomimy makro w sesji działającej w trybie headless (nie próbowałem, ale nie widzę przeszkód).

Kod: Zaznacz cały

sub testMergeFiles()

  doc =  StarDesktop.loadComponentFromURL("private:factory/simpress", "_blank", 0, Array())
  mergeFromURL(doc, "file:///c:/users/my_user/slides1.odp")
  mergeFromURL(doc, "file:///c:/users/my_user/slides2.odp")
  doc.DrawPages.remove(doc.DrawPages.getByIndex(0))
  doc.storeAsURL("file:///c:/users/my_user/output.odp", Array())
  doc.close(true)

end sub

sub mergeFromURL(doc1, url)

  dsp = createUnoService( "com.sun.star.frame.DispatchHelper")
  dt = 1000

  ctr1 = doc1.getCurrentController()
  frame1 = ctr1.Frame

  doc2 = StarDesktop.loadComponentFromURL(url, "_blank", 0, Array())
  ctr2 = doc2.getCurrentController()
  frame2 = ctr2.Frame

  dsp.executeDispatch(frame1, ".uno:DiaMode", "", 0, Array())
  dsp.executeDispatch(frame2, ".uno:DiaMode", "", 0, Array())
  
  for i = 0 to doc2.DrawPages.Count-1

  	page2 = doc2.DrawPages.getByIndex(i)
  	ctr2.setCurrentPage(page2)
	dsp.executeDispatch(frame2, ".uno:Copy", "", 0, Array())
	wait(dt)
            
  	page1 = doc1.DrawPages.getByIndex(doc1.Drawpages.Count-1)
  	ctr1.setCurrentPage(page1)
	dsp.executeDispatch(frame1, ".uno:Paste", "", 0, Array())
	wait(dt)
  	
  next i

  dsp.executeDispatch(frame1, ".uno:DrawingMode", "", 0, Array())
  doc2.close(false)

end sub
Słowo komentarza: uruchamiamy makro testMergeFiles, które zakłada nową prezentację, a potem po kolei dołącza do niej slajdy ze wskazanych plików. Na koniec usuwa pierwszy slajd, założony przy utworzeniu dokumentu, po czym zapisuje i zamyka całość.
Czas 1000 (1 sek.) być może da się skrócić, ale nie do zera, bo wtedy schowek "nie zdąży" otrzymać danych przed zmianą fokusu kontrolera, i nici z operacji.
Nazwy plików są zakodowane na sztywno i nie są przetwarzane w pętli tylko osobnymi wywołaniami, ale to jest tylko test demonstrujący ideę.

Drugi szkic został napisany w Pythonie. Oto on:

Kod: Zaznacz cały

# coding: utf8

import sys
import os
import time
import uno
import pyuno

def mergeFromURL(desk, dispatcher, doc1, url, dt = 1.0):

    ctr1 = doc1.getCurrentController()
    frame1 = ctr1.Frame

    doc2 = desk.loadComponentFromURL(url, "_blank", 0, ())
    ctr2 = doc2.getCurrentController()
    frame2 = ctr2.Frame

    dispatcher.executeDispatch(frame1, ".uno:DiaMode", "", 0, ())
    dispatcher.executeDispatch(frame2, ".uno:DiaMode", "", 0, ())

    for i in range(doc2.DrawPages.Count):

        page2 = doc2.DrawPages.getByIndex(i)
        ctr2.setCurrentPage(page2)
        dispatcher.executeDispatch(frame2, ".uno:Copy", "", 0, ())
        time.sleep(dt)
        
        page1 = doc1.DrawPages.getByIndex(doc1.DrawPages.Count-1)
        ctr1.setCurrentPage(page1)
        dispatcher.executeDispatch(frame1, ".uno:Paste", "", 0, ())
        time.sleep(dt)

    dispatcher.executeDispatch(frame1, ".uno:DrawingMode", "", 0, ())
    doc2.close(False)

def fileNameToUrl(fname):
    if fname[0:7] != 'file://':
        fname = os.path.realpath(fname)
        return pyuno.systemPathToFileUrl(fname)
    else:
        return fname

def mergeFiles(ctx, desk, filelist, outname):
    doc =  desk.loadComponentFromURL("private:factory/simpress", "_blank", 0, ())
    dispatcher = ctx.ServiceManager.createInstanceWithContext("com.sun.star.frame.DispatchHelper", ctx)
    for fname in filelist:
        mergeFromURL(desk, dispatcher, doc, fileNameToUrl(fname))
    doc.DrawPages.remove(doc.DrawPages.getByIndex(0))
    doc.storeAsURL(fileNameToUrl(outname), ())
    doc.close(False)

def asMacro():
    ctx = XSCRIPTCONTEXT.getComponentContext()
    desk = XSCRIPTCONTEXT.getDesktop()
    infiles = [
                    "c:/users/my_user/slides1.odp",
                    "c:/users/my_user/slides2.odp",
                  ]
    outfile = "c:/users/my_user/output.odp"
    mergeFiles(ctx, desk, infiles, outfile)

def asService():
    assert len(sys.argv) >= 2
    localContext = uno.getComponentContext()
    resolver = localContext.ServiceManager.createInstanceWithContext("com.sun.star.bridge.UnoUrlResolver", localContext)
    ctx = resolver.resolve( "uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext" )
    desk = ctx.ServiceManager.createInstanceWithContext( "com.sun.star.frame.Desktop", ctx)
    infiles = sys.argv[1 : -1]
    outfile = sys.argv[-1]
#    infiles = [
#                    "c:/users/my_user/slides1.odp",
#                    "c:/users/my_user/slides2.odp",
#                  ]
#    outfile = "c:/users/my_user/output.odp"
    mergeFiles(ctx, desk, infiles, outfile)

if __name__ == '__main__':
    asService()
I kilka zdań komentarza:
funkcja asMacro() jest w miarę dokładnym odpowiednikiem Basicowego testMergeFiles(). Ale ... nie działa; konkretnie -- generuje dokument z jednym pustym slajdem. Przypuszczam, że time.sleep pracuje w tym samym wątku co office'owy dispatcher, w przeciwieństwie do Basicowej funkcji wait. Ale to by wymagało dokładniejszego sprawdzenia.
Funkcja asService jest przeznaczona do współpracy z procesem serwerowym Open/LibreOffce'a. Jest kilka metod uruchomienia takiego procesu, ja używałem komunikacji przez port tcp 2002. Office startował z polecenia

Kod: Zaznacz cały

libreoffice.exe -headless "-accept=socket,host=localhost,port=2002;urp;"
(nazwę pliku wykonywalnego należy w razie czego poprzedzić ścieżką, zależnie od szczegółów instalacji pakietu).
Skrypt jest zorganizowany tak, że uruchamia funkcję asService automatycznie. Wymagany do uruchomienia wiersz poleceń ma postać

Kod: Zaznacz cały

"scieżka do libreoffice\python.exe" skrypt.py plik1.odp plik2.odp ... plikN.odp output.odp
co oznacza, że:
  • uruchamiany Python powinien należeć do instalacji Open/LibreOffice. Chyba że pracujesz na Linuksie, a Libre jest zainstalowany z paczki przygotowanej przez Twoje distro -- wtedy Python systemowy jest OK;
  • skrypt.py to nazwa pliku ze skryptem. W zasadzie dowolna;
  • dalej następują argumenty wywołania. Wszystkie, z wyjątkiem ostatniego, są ścieżkami do łączonych dokumentów. Ostatni jest ścieżką pliku wynikowego. Skrypt zamieni je na ścieżki bezwzględne, a następnie na URL-e;
  • w związku z tym argument wywołania musi być co najmniej jeden -- co jest sprawdzane.
Skrypt uruchomiony tym sposobem jest skuteczny. W tym przypadku sleep pracuje w wątku klienta, więc nie zamraża działań dispatchera.
Testowałem pod Windowsem; otwieranie plików daje efekty wizualne. Jeżeli serwer pracuje na Linuksie bez X-ów, zdarzyć się to nie może siłą rzeczy.

Skrypt kopiuje zawartość tekstową i graficzną poszczególnych stron, ale ignoruje tzw. Master Pages (czyli wzorce stron) i być może style (nie sprawdzałem dokładnie). W każdym razie robi tyle samo, co ręczne Copy&Paste.

Przy rozbudowie warto pamiętać, że LO ma wbudowanego Pythona 3.x, a Apache OO pozostaje wciąż z wersją 2.x.

// edit
W trybie headless na Linuksie skrypt pracuje, ale jakby nie wszystko kopiował. Czyżby .uno:Copy/Paste bazowały na schowku GUI? nie mogę wykluczyć...
Jakimś rozwiązaniem zawsze pozostaje lekki serwer X w stylu xvfb albo vncserver. Chociaż chciałoby się zrobić to całkiem po cichu.
JJ
LO (7.6|24.2) ∙ Python (3.12|3.10) ∙ Unicode 15 ∙ LᴬTEX 2ε ∙ XML ∙ Unix tools ∙ Linux (Rocky|CentOS)
ODPOWIEDZ