8. GRAFIKA I MULTIMEDIJA

 

Primeri koda:

 

File:PythonOnSymbianBookExampleCode CanvasShapes.zip

File:PythonOnSymbianBookExampleCode CanvasText.zip

File:PythonOnSymbianBookExampleCode ImageEditing.zip

File:PythonOnSymbianBookExampleCode PyMPlayer.zip

File:PythonOnSymbianBookExampleCode PySymCamera.zip

File:PythonOnSymbianBookExampleCode RecordVideo.zip

File:PythonOnSymbianBookExampleCode ScreenShot.zip

File:PythonOnSymbianBookExampleCode SoundFiles.zip

File:PythonOnSymbianBookExampleCode TakeMaxresPhoto.zip

File:PythonOnSymbianBookExampleCode TakePhoto.zip

File:PythonOnSymbianBookExampleCode TextToSpeech.zip

File:PythonOnSymbianBookExampleCode UsingViewFinder.zip

 

 

8.1. Uvod

Python podržava operacije sa crtanje primitivnih oblika i teksta, fotografisanje, prikazivanje i obradu slika, kao i snimanje i puštanje zvučnih zapisa. Sa porastom mogućnosti mobilnih uređaja u naprednim multimedijalnim operacijama, kreativne mogućnosti su skoro beskrajne.

 

Ovo poglavlje prikazuje svaku od ovih operacija kroz osnovne primere.

 

 

8.2. Crtanje na platnu (Canvas)

Canvas je kontrola korisničkog interfejsa najnižeg mogućeg nivoa dostupnog u PyS60. Sačinjen je od oblasti na kojoj se može crtati na ekranu. Na toj oblasti se mogu prikazati druge UI komponente, uključujući osnovna crtanja primitivni oblika, teksta, slika itd.


Platno može presresti ključne događaje iz 5-smerne tastature prisutne na većini Symbian uređaja (5-smerna tastatura je zato što možemo signalisati levo, desno, gore, dole i ima centarni 'select' taster). Canvas ima bind() metod koji se može koristiti da se poveže funkcija sa ključnim događajem.

 

canvas.bind(EKeyUpArrow, your_up_function)
canvas.bind(EKeyDownArrow, your_down_function)
canvas.bind(EKeyLeftArrow, your_left_function)
canvas.bind(EKeyRightArrow, your_right_function
canvas.bind(EKeySelect, your_select_function)

 

Neki uređaju, poput onih sa ekranima osetljivim na dodir (touch screen) nemaju fizičke softkey niti 'strelica tastere'. Za ove, možemo koristiti platno da prikažemo virtualni direkcioni pad (podlogu) (ako je aplikacija u full screen modu, direkcioni pad je propraćen sa dva virtualna softkey-a, kao što se vidi na sledećoj slici) (Slika 25). Pad se može omogućiti ili onemogućiti podešavanjem appuifw.app.directional_pad na True ili False, respektivno.

 

 

Slika 25 – Virtualni direkcioni pad

 

         338px-PythonOnSymbian_Normalscreen_Dpad.jpg            338px-PythonOnSymbian_Fullscreen_Dpad.jpg  
             Direkcioni pad za normal screen                   Direkcioni pad za full screen

 

Aplikacije mogu crtati na Canvas koristeći osnovne 'grafičke primitive' ili oblike. Dostupni oblici uključuju liniju, pravougaonik, poligon, elipsu, luk, odsečak kružnice i tačku. Metode koje se koriste za crtanje oblika i njihovi argumenti imaju generalnu formu method(coordseq[, <options>]).

 

Naredni primer pokazuje kako nacrtati neke oblike, a rezultujući ekran je prikazan na slici ispod (Slika 26).

 

import e32, appuifw
 
app_lock = e32.Ao_lock()
def quit():
  app_lock.signal()
appuifw.app.exit_key_handler = quit
 
appuifw.app.screen = 'full'
 
canvas = appuifw.Canvas()
appuifw.app.body = canvas
 
#linija((x1,y1,x2,y2), širina)
canvas.line((30,45,160,15), 0)
 
#pravougaonik((x1,y1,x2,y2), boja)
canvas.rectangle((30,45,190,130), fill=0xCC55AA)
 
#elipsa((x1,y1,x2,y2), boja)
canvas.ellipse((22,250,78,280), fill=0x337700)
 
#tačka((x,y), (R,G,B), širina)
canvas.point((200,180), (243,46,113), width=6)
 
app_lock.wait()

 

 

Slika 26 – Nekoliko primitivnih oblika iscrtanih na ekranu

 

                                   337px-PythonOnSymbian_Canvas_shapes.jpg

 

Canvas se takođe može koristiti i za crtanje teksta; ova mogućnost se često koristi za prikaz teksta sa prilagođenim font-om, veličinom ili bojom, na proizvoljnoj poziciji na ekranu. Canvas obezbeđuje text() metod za podešavanje teksta (font  i boja) koji će se crtati, i measure_text() za određivanje veličine i položaja koji će se zauzeti nekim zadatim tekstom ako se iscrta zadatim font-om.

 

Naredni primer pokazuje kako nacrtati tekst 'Symbian' određenim font-om, određene boje i pozicije, a rezultat je prikazan na slici ispod (Slika 27).

 

import appuifw, e32
 
app_lock = e32.Ao_lock()
def quit():
  app_lock.signal()
appuifw.app.exit_key_handler = quit
 
canvas = appuifw.Canvas()
appuifw.app.body = canvas
 
#tekst((x,y), Unicode string, boja, font=(ime,veličina,stil))
canvas.text((40,60), u"Symbian", 0x007F00, font=(u'Nokia Hindi S60',45,appuifw.STYLE_BOLD))
 
app_lock.wait()

 

 

Slika 27 – Prilagodljiv tekst iscrtan na ekranu

 

                                   336px-PythonOnSymbian_Canvas_example.jpg

 

Možemo definisati posebnu funkciju da prosledi redraw callback konstruktoru za Canvas. Ona određuje operacije koje trebaju biti izvršene kada se na platnu treba ponovo crtati (što se događa nakon što bude 'pomračeno' od strane nečeg drugog na ekranu.) Evo dela koda da to pojasni:

 

...
def handle_redraw(rect):
  c.clear(0xFF0000)
c = appuifw.Canvas(redraw_callback=handle_redraw)
appuifw.app.body = c
...

 

 

8.3. Prikaz i rukovanje sa Image objektima (slikama)

Klasa Image, koja je definisana u graphics modulu, se koristi za kreiranje, uređivanje i sačuvavanje (to save) slika. Objekti slika (Image objects) se kreiraju pomoću new() metoda, gde se određuje željena veličina na sledeći način:

 

 

import graphics
 
img = graphics.Image.new((360, 640))

 

Slika se može uređivati na razne načine. Može joj se dodati tekst, na njoj se mogu nacrtati razni oblici i može se transponovati, rotirati i promeniti joj veličinu. Sledeći primer pokazuje kako se sve ove operacije mogu izvesti, a naredna slika prikazuje rezultat (Slika 28):

 

import graphics, appuifw, e32
 
app_lock = e32.Ao_lock()
def quit():
  app_lock.signal()
appuifw.app.exit_key_handler = quit
 
#Open an image
img = graphics.Image.open("C:\\Data\\my_image.jpg")
 
#Draw a rectangle on it
img.rectangle((30,45,110,100), fill=0xCC55AA)
 
#Rotate it by 90 degrees and resize it to a quarter of the original size
#Other rotation options include ROTATE_180 and ROTATE_270
img = img.transpose(graphics.ROTATE_90)
img = img.resize((img.size[0]/2, img.size[1]/2))
 
#Display text on it
img.text((20, 45), u"Image editing", font="title")
 
#Save the image
img.save("C:\\Data\\my_edited_image.jpg", quality=100)
 
def handle_redraw(rect):
  canvas.blit(img)
 
#Show the result on the screen
canvas = appuifw.Canvas(redraw_callback=handle_redraw)
appuifw.app.body = canvas
 
app_lock.wait()

 

 

Slika 28 – Primer uređivanja slike ’Pre’ i ’Posle’

 

     PythonOnSymbian_my_edited_image.jpgPythonOnSymbian_my_image.jpg

                    

 

 

 

 

 

 

 

 

 

 

 

 

 

 

        Originalna slika                                                      Slika nakon uređivanja

 

 

8.4. Screenshot capture (Fotografisanje ekrana)

Često je korisno hvatanje screenshot-a naše aplikacije za svrhe promocije, debug-ovanja i sl. Ovo se može raditi iz Python aplikacije koristeći screenshot() funkciju graphics modula.

 

Funkcija screenshot() vraća objekat slike, koji se može koristiti da se sačuva fotografija u JPEG ili PNG formatu. Po default-u, fotografija se sačuva sinhrono; callback se može specifikovati kao opcioni parametar da bi fajl sačuvali asinhrono.


Sledeći primer prikazuje slikanje i sinhrono čuvanje screenshot-a u JPEG formatu:

 

import graphics
 
#Take the screenshot
img = graphics.screenshot()
#Save it to the specified path
img.save(u"C:\\Data\\screenshot.jpg")

 

 

8.5. Upotreba kamere

PyS60 pruža pristup kameri uređaja preko modula camera i obezbeđuje funkcije za fotografisanje i čuvanje tih fotografija i video snimaka. Viewfinder se može koristiti za prikaz onoga što se fotografiše. Nakon što kamera više nije potrebna, mora se osloboditi da bi je mogle koristiti druge aplikacije.

 

8.5.1. Korištenje Viewfinder-a

Jedan od najvažnijih aspekata fotografije i videografije je mogućnost obuhvatiti subjekat po želji. Viewfinder nam omogućuje upravo to, tako što prikazuje sliku koja će se uhvatiti na ekranu uređaja (Slika 29).

 

Slika 29 – Viewfinder u akciji

 

800px-PythonOnSymbian_Illustrate_viewfinder.jpg

 

Sledeći isečak koda demonstrira kako se upotrebljava viewfinder. Definiše funkciju koja plasira informacije primljene iz kamere u telo aplikacije (koje je instanca od Canvas) i prosleđuje to kao argument start_finder() funkciji. Veličina viewfinder-a se takođe može odrediti prosleđivanjem tuple-a zahtevane širine/visine u pikselima, opcionom argumentu funkcije size.

 

import camera, appuifw, e32
 
app_lock = e32.Ao_lock()
def quit():
  #Zatvori 'viewfinder'
  camera.stop_finder()
  #Zatvori kameru da bi je drugi programi mogli koristiti
  camera.release()
  app_lock.signal()
appuifw.app.exit_key_handler = quit
 
#Definiši funkciju za prikazivanje 'viewfinder'-a; zapravo ono samo prikazuje sliku na 'Canvas'-u
def vf(im):
  appuifw.app.body.blit(im)
 
#Podesi telo aplikacije na 'Canvas'
appuifw.app.body = appuifw.Canvas()
 
#Pokreni viewfinder zadrži upaljen 'backlight'
camera.start_finder(vf, backlight_on=1)
 
app_lock.wait()

 

8.5.2. Fotografisanje

 

Fotografije se hvataju (fotografišu) sa take_photo() funkcijom. Nakon fotografisanja, možemo je sačuvati baš kao i bilo koju drugu sliku.


Funkcija take_photo() ima mnoštvo (opcionalnih) argumenata pomoću kojih se mogu odrediti razna podešavanja. Puna forma funkcije je take_photo([mode, size, zoom, flash, exposure, white_balance, position]); detaljnije značenje svakog argumenta je opisano u dokumentaciji (http://pys60.garage.maemo.org/doc/s60/module-camera.html).


Sledeća tabela daje kratak opis svakog argumenta (Tabela 13 ):

 

 

Tabela 13 – Argumenti ’take_photo()’ funkcije

 

Argument

Opis

Moguće vrednosti

mode

Mod prikazivanja slike

'RGB12', 'RGB16', 'RGB', 'JPEG_Exif', 'JPEG_JFIF'; vraća se od strane image_modes() funkcije.

size

Rezolucija slike

Dvo-elementni tuple vraćen od strane image_sizes()funkcije.

zoom

Zum faktor

Od  0 do vrednosti vraćene od strane max_zoom() funkcije.

flash

Podešavanje blica

'none', 'auto', 'forced', 'fill_in', 'red_eye_reduce'; vraćeno od strane flash_modes() funkcije.

exposure

Podešavanje nivoa izloženosti

'auto', 'night', 'backlight', 'center'; vraćeno od strane exposure_modes() funkcije.

white_balance

Temperatura boje pri trenutnoj svetlosti

'auto', 'daylight', 'cloudy', 'tungsten', 'fluorescent', 'flash'; vraćeno od strane white_balance_modes() funkcije.

position

Određivanje koja se kamera koristi (ako uređaj ima više od jedne)

Od 0 do vrednosti vraćene od strane cameras_available() funkcije minus 1.

 

 

import camera, appuifw, e32
 
app_lock = e32.Ao_lock()
def quit():
  # Zatvori kameru da bi je drugi programi mogli koristiti
  camera.release()
  app_lock.signal()
appuifw.app.exit_key_handler = quit
 
#Fotografiši
photo = camera.take_photo('RGB', (1024, 768))
 
#Sačuvaj je u maksimalnom kvalitetu
photo.save("C:\\Data\\my_photo", quality=100)
 
app_lock.wait()

 

Na nekim novijim Symbian uređajima, kako bismo mogli fotografisati sa maksimalnom rezolucijom koju podržava kamera, moramo:

  1. Prebaciti u landscape mod.
  2. Import-ovati modul camera.
  3. Fotografisati u „JPEG_Exit“ formatu.
  4. Sačuvati je pisanjem informacija o njoj u fajl, ne kao Image objekat.

Sledeći primer pokazuje kako fotografisati sa maksimalnom rezolucijom. Kao što je naznačeno u prethodnoj tabeli, funkcija image_sizes() vraća listu mogućih rezolucija fotografija. Pozivajući je sa mode-om kao argumentom, u našem slučaju „JPEG_Exit“, vraća listu svi rezolucija koje su kompatabilne sa tim mode-om. Fotografisati se može odabirom „Take photo“ iz menija Options.

 

import appuifw, e32
 
#Pređi u landscape mod
appuifw.app.orientation = "landscape"
 
import camera
 
app_lock = e32.Ao_lock()
def quit():
  #Oslobodi kameru da bi je drugi programi mogli koristiti
  camera.release()
  app_lock.signal()
appuifw.app.exit_key_handler = quit
 
#Funkcija za fotografisanje
def take_picture():
  #Fotografiši
  photo = camera.take_photo('JPEG_Exif', camera.image_sizes('JPEG_Exif')[0])
  #Sačuvaj
  f = open(savepath, "wb")
  f.write(photo)
  f.close()
 
savepath = u"C:\\Data\\photo.jpg" 
#Ovo je putanja i ime za skladištenje slike
 
appuifw.app.body = appuifw.Canvas()
 
appuifw.app.menu=[(u"Take photo", take_picture)]
 
app_lock.wait()

 

8.5.3. Snimanje video zapisa

Snimanje video zapisa se vrši kroz start_record() funkciju camera modula. Potrebno je pre pozivanja funkcija pokrenuti viewfinder. Sledeći isečak koda koristi tajmer da snimi video zapis od 10 sekundi, nakon kojih se snimanje zaustavlja.

 

import camera, appuifw, e32
 
app_lock = e32.Ao_lock()
def quit():
  #Poništi tajmer kada korisnik izađe, ako nije istekao
  timer.cancel()
  #Zatvori 'viewfinder'
  camera.stop_finder()
  #Oslobodi kameru da bi je drugi programi mogli koristiti
  camera.release()
  app_lock.signal()
appuifw.app.exit_key_handler = quit
 
#Funkcija za prikazivanje 'viewfinder'-a
def vf(im):
  appuifw.app.body.blit(im)
 
#Funkcija koja će biti prosleđena kao argument 'start_record'-u:; koristi se #za rukovanje mogućim greškama i praćenje trenutnog statusa
def video_callback(err, current_state):
        global control_light  
        if current_state == camera.EPrepareComplete:
          control_light=1
        else:
          pass
 
#Putanja gde će video snimak biti skladišten
video_savepath = u"C:\\Data\\video.mp4"
 
appuifw.app.body = appuifw.Canvas()
 
#Pokreni 'viewfinder'
camera.start_finder(vf)
 
#Počni snimanje
video = camera.start_record(video_savepath, video_callback)
 
#Kreiraj tajmer
timer = e32.Ao_timer()
 
#Snimaj 10 sekundi, onda stani
timer.after(10, lambda:camera.stop_record())
 
app_lock.wait()

 

8.5.4. PySymCamera – Primer interfejsa kamere za jezičcima

Evo primera aplikacije koja može fotografisati i snimati video zapis. Ona ujedinjuje gore pominjano u potpuno funkcionalnu aplikaciju i koristi jezičke (tabs) za prebacivanje između fotografija i video zapisa.

 

import camera, appuifw, e32, os, graphics, sysinfo
from key_codes import *
 
app_lock = e32.Ao_lock()
def quit():
  appuifw.app.set_tabs([], None)
  camera.stop_finder()
  camera.release()
  app_lock.signal()
appuifw.app.exit_key_handler = quit
 
#Promenljiva "recording" vodi računa o tome da li se video zapis trenutno #snima ili ne
recording = False
 
def number_of_files():
  #Fotografije i video zapisi će biti skladišteni u C:\Data,
  #i trenutni broj se dodaje na kraj svakog imena fajla
  global number_of_photos, number_of_videos
  number_of_photos = 0
  number_of_videos = 0
  for f in os.listdir("C:\\Data"):
        if os.path.isfile("C:\\Data\\" + f):
               if f.startswith("my_photo") and f.endswith(".jpg"):
                      number_of_photos += 1
               if f.startswith("my_video") and f.endswith(".mp4"):
                      number_of_videos += 1
 
#Definiši funkciju koja će biti pozvana kada se pritisne 'Select' taster, za fotografisanje i snimanje
def take():
  global recording
 
  if mode == "photocamera":
        number_of_files()
        photo = camera.take_photo('RGB', camera.image_sizes()[0], zoom, flash)
        photo.save("C:\\Data\\my_photo" + str(number_of_photos + 1) + ".jpg")
        photocamera()
  elif mode == "videocamera":
        if not recording:
               number_of_files()
               camera.start_record("C:\\Data\\my_video" + str(number_of_videos + 1) + ".mp4", video_callback)
               recording = True
        elif recording:
               camera.stop_record()
               recording = False
 
#Funkcija za povećavanje i smanjivanje 'zoom' nivao
def zoom_up():
  global zoom
 
  if mode == "photocamera":
        if zoom + camera.max_zoom() / 2 <= camera.max_zoom():
               zoom += camera.max_zoom() / 2
               photo = camera.take_photo('RGB', camera.image_sizes()[-1], zoom, flash='none')
               camera.stop_finder()
               photocamera()       
 
def zoom_down():
  global zoom
 
  if mode == "photocamera":
        if zoom  - camera.max_zoom() / 2 >0:
               zoom -= camera.max_zoom() / 2
               photo = camera.take_photo('RGB', camera.image_sizes()[-1], zoom, flash='none')
               camera.stop_finder()
               photocamera()
 
def handle_tabs(index):
  global recording, zoom
  if index == 0:
        if recording:
               camera.stop_record()
               recording = False
        camera.stop_finder()
        zoom = 0
        photocamera()
  if index == 1:
        camera.stop_finder()
        videocamera()
appuifw.app.set_tabs([u"Photo", u"Video"], handle_tabs)
 
#Kreiraj novu, prazni sliku koja će se koristiti za 'double buffering'
img = graphics.Image.new(sysinfo.display_pixels())
 
#Definiši funkciju za prikazivanje 'viewfinder'-a
def vf(im):
  img.blit(im)
  handle_redraw(())
 
def handle_redraw(rect):
  global canvas
  canvas.blit(img)
 
canvas = appuifw.Canvas(redraw_callback=handle_redraw)
appuifw.app.body = canvas
 
canvas.bind(EKeyUpArrow, zoom_up)
canvas.bind(EKeyDownArrow, zoom_down)
canvas.bind(EKeySelect, take)
 
#Definiši callback za snimanje video zapisa
def video_callback(err, current_state):
  global control_light
  if current_state == camera.EPrepareComplete:
        control_light=1
  else:
        pass
 
#Zoom je u početku 0
zoom = 0
#Blic je po default-u onemogućen
flash = "none"
flash_enabled = "  "
flash_disabled = "x "
 
#Funkcija za omogućavanje i onemogućavanje blica
def set_flash(option):
  global flash, flash_enabled, flash_disabled
  if option == True:
        flash = "forced"
        flash_enabled = "x "
        flash_disabled = "  "
  if option == False:
        flash = "none"
        flash_enabled = "  "
        flash_disabled = "x "
  appuifw.app.menu = [(u"Flash", ((flash_enabled + u"Enabled", lambda:set_flash(True)),
                      (flash_disabled + u"Disabled", lambda:set_flash(False))))]
 
#Definiše 2 glavne funkcije, jednu za 'photo mode' i jednu za 'video mode'
def photocamera():
#Definiši promenljivu koja skladišti trenutni mod, koja se koristi kada se taster pritisne
global mode
mode = "photocamera"
 
camera.start_finder(vf, backlight_on=1)
 
appuifw.app.menu = [(u"Flash", ((flash_enabled + u"Enabled", lambda:set_flash(True)),
                          (flash_disabled + u"Disabled", lambda:set_flash(False))))]
 
def videocamera():
  #Definišti promenljivu koja skladišti trenutni mod, koja se koristi kada se taster pritisne
  global mode
  mode = "videocamera"
 
  camera.start_finder(vf, backlight_on=1)
 
  appuifw.app.menu = []
 
photocamera()
 
app_lock.wait()

 

Analizirajmo sada kod, da vidimo šta radi i kako funkcioniše:

 

app_lock = e32.Ao_lock()
def quit():
  appuifw.app.set_tabs([], None)
  camera.stop_finder()
  camera.release()
  app_lock.signal()
appuifw.app.exit_key_handler = quit

 

Prvo, rukujemo s onim šta se događa kada se aplikacija zatvara. Kreiramo aktivni objektno-bazirani sinhonizacijski servis (app_lock) i definišemo funkciju za pozivanje kada se aplikacija zatvori. Pri zatvaranju, funkcija mora da:

 

  1. se otarasi jezičaka, kao što je objašnjeno u Poglavlju 4.11.
  2. zaustavi viewfinder
  3. oslobodi kameru
  4. pozove signal() metod servisa, govoreći skripti da se završi.

Dodeljujemo funkciju desnom mekom testeru (softkey), da obradi zahtev za izlazak od strane korisnika.

 

recording = False

 

Ovaj flag vodi računa o tome da li se trenutno snima video zapis ili ne. Ovo nam je potrebno kako bismo znali šta da radimo kada se pritisne selekcioni taster u video modu.

 

def number_of_files():
  #Fotografije i video zapisi će biti skladišteni u C:\Data,
    #i trenutni broj se dodaje na kraj svakog imena fajla
  global number_of_photos, number_of_videos
  number_of_photos = 0
  number_of_videos = 0
  for f in os.listdir("C:\\Data"):
        if os.path.isfile("C:\\Data\\" + f):
               if f.startswith("my_photo") and f.endswith(".jpg"):
                      number_of_photos += 1
               if f.startswith("my_video") and f.endswith(".mp4"):
                      number_of_videos += 1

 

Ovo je funkcija koju koristimo da odredimo broj kojim ćemo imenovati nove fotografije i video snimke. Funkcija jednostavno broji koliko fotografija i video snimaka ima na lokaciji gde čuvamo fotografije i video snimke. Ukoliko se ime fajla na toj lokaciji podudara sa određenim formatom (u ovom slučaju, ako počinje sa „my_photo“/“my_video“ i ima „.jpg“/“.mp4“ ekstenziju), promenljiva brojača se povećava. Vrednost promenljive brojača (number_of_photos ili number_of_videos) se povećava za 1 i dodeljuje imenu povezanog tipa fajla kada se čuva.

 

def take():
  global recording
 
  if mode == "photocamera":
        number_of_files()
        photo = camera.take_photo('RGB', camera.image_sizes()[0], zoom, flash)
        photo.save("C:\\Data\\my_photo" + str(number_of_photos + 1) + ".jpg")
        photocamera()
  elif mode == "videocamera":
        if not recording:
               number_of_files()
               camera.start_record("C:\\Data\\my_video" + str(number_of_videos + 1) + ".mp4", video_callback)
               recording = True
        elif recording:
               camera.stop_record()
               recording = False

 

Ovo je najvažnija funkcija u čitavom programu. Poziva se kada se selekcioni taster pritisne, i ponaša se različito, u zavisnosti od toga da li je pozvana u 'photocamera' mod ili 'videocamera' mod. U prvom slučaju, izračunava broj fotografija unutar direktorijuma u kom se čuvaju, zatim fotografiše, sačuva je i restart-uje kameru. U drugom slučaju, prvo proverava da li se video zapis trenutno snima. Ukoliko se ne snima, izračunava broj video zapisa u direktorijumu u kojem se čuvaju i počinje snimanje. Ukoliko se video zapis snima, jednostavno zaustavlja snimanje.

 

def zoom_up():
  global zoom
 
  if mode == "photocamera":
        if zoom + camera.max_zoom() / 2 <= camera.max_zoom():
               zoom += camera.max_zoom() / 2
               photo = camera.take_photo('RGB', camera.image_sizes()[-1], zoom, flash='none')
               camera.stop_finder()
               photocamera()       
 
def zoom_down():
  global zoom
 
  if mode == "photocamera":
        if zoom - camera.max_zoom() / 2 >= 0:
               zoom -= camera.max_zoom() / 2
               photo = camera.take_photo('RGB', camera.image_sizes()[-1], zoom, flash='none')
               camera.stop_finder()
               photocamera()

 

Ove dve funkcije su komplementarne. Koriste se za zoomin i zoomout, respektivno. Pošto je podešavanje zoom-a različitog od nule moguće jedino pri fotografisanju, prvo proveravamo da li je aplikacija u 'photocamera' modu. Ako jeste, proveravamo da li se nivo zoom-a može promeniti u odnosu na trenutni nivo. Ako može, trenutni nivo se modifikuje za pola od maksimalnog nivoa zoom-a. Nakon toga se fotografiše dummy fotografija i kamera se restart-uje kako bi viewfinder displej bio na novom nivou zoom-a.

 

def handle_tabs(index):
  global recording, zoom
  if index == 0:
        if recording:
               camera.stop_record()
               recording = False
        camera.stop_finder()
        zoom = 0
        photocamera()
  if index == 1:
        camera.stop_finder()
        videocamera()
appuifw.app.set_tabs([u"Photo", u"Video"], handle_tabs)

 

Ovaj deo koda rukuje sa jezičcima (tabs). Funkcija se poziva kad god korisnik prelazi između dva jezička koje aplikacija koristi (jedan za 'photocamera' mod i drugi za 'videocamera' mod). Ako je izabran jezičak za fotografisanje, svako trenutno snimanje video zapisa se zaustavlja i kamera se restart-uje u fotografski mod. Ukoliko je video jezičak izabran, s obzirom da nema snimanja koje bi se zaustavilo, aplikacija jednostavno prebaci na taj jezičak restart-ujući se u video mod. Zadnji red koda kreira ta dva jezička.

 

img = graphics.Image.new(sysinfo.display_pixels())

 

Kada prikazujemo viewfinder, visoka brzina pri kojoj se podaci šalju iz kamere ka ekranu može izazvati treperenje. Ovo se rešava primenom koncepta double buffering-a (http://en.wikipedia.org/wiki/Double_buffering). Umesto da prikazujemo podatke viewfinder-a direktno na ekran, prvo ih stavljamo u img i onda prikazujemo to.

 

def vf(im):
  img.blit(im)
  handle_redraw(())

 

U prethodnom isečku koda definišemo funkciju koja uzima podatke iz kamere u formi slike i iscrtava to na drugu sliku (prva faza double buffering-a). Onda poziva canvas-ovu redraw callback funkciju, koja stavlja sliku na canvas, te je prikazuje na ekranu (druga faza double buffering-a).

 

canvas.bind(EKeyUpArrow, zoom_up)
canvas.bind(EKeyDownArrow, zoom_down)
canvas.bind(EKeySelect, take)

 

Sada povezujemo određene tastere sa određenim akcijama. bind() metod Canvas instance veže (binds) funkciju za keycodes. Na primer, kada se pritisne taster up arrow (strelica gore), poziva se funkcija take().

 

#Definiši callback za snimanje video zapisa
def video_callback(err, current_state):
  global control_light
  if current_state == camera.EPrepareComplete:
        control_light=1
  else:
        pass

 

Povratna funkcija će biti pozvana sa error kodom i informacijama o statusu kao parametrom. Mora biti prisutna kako bi uređaj bio spreman da započne snimanje video zapisa. Lista mogućih stanja je dostupna u PyS60 documentation (http://pys60.garage.maemo.org/doc/s60/module-camera.html

 

#Zoom je u početnku 0
zoom = 0
#Blic je onemogućen po default-u
flash = "none"
flash_enabled = "  "
flash_disabled = "x "
 
#Funkcija za omogućavanje i onemogućavanje blica
def set_flash(option):
  global flash, flash_enabled, flash_disabled
  if option == True:
        flash = "forced"
        flash_enabled = "x "
        flash_disabled = "  "
  if option == False:
        flash = "none"
        flash_enabled = "  "
        flash_disabled = "x "
appuifw.app.menu = [(u"Flash", ((flash_enabled + u"Enabled", lambda:set_flash(True)),
                          (flash_disabled + u"Disabled", lambda:set_flash(False))))]

 

Ovde određujemo da je nivo zoom-a an početnu 0 i blic je onemogućen na početku. Onda definišemo funkciju set_flash(option) koja omogućuje ili onemogućuje blic. Važni redovi ove funkcije su flash = "forced" i flash = "none" (“forced” i “none” su modovi blica) zato što oni uključuju i isključuju blic, zavisno od Boolean vrednosti koja se prosleđuje kao argument.

 

def photocamera():
  # Definiši promenljivu koja skladišti trenutni mod, koja se koristi kada se taster pritisne
  global mode
  mode = "photocamera"
 
  camera.start_finder(vf, backlight_on=1)
 
  appuifw.app.menu = [(u"Flash", ((flash_enabled + u"Enabled", lambda:set_flash(True)),
                          (flash_disabled + u"Disabled", lambda:set_flash(False))))]

 

Ovo je funkcija koja se poziva kada se kamera prebaci u fotografski mod. Podešava trenutni mod u 'photocamera', pokreće viewfinder i sukladno tome podešava meni.

 

def videocamera():
  # Definiši promenljivu koja skladišti trenutni mod, koja se koristi kada se taster pritisne
  global mode
  mode = "videocamera"
 
  camera.start_finder(vf, backlight_on=1)
 
  appuifw.app.menu = []

 

Ova funkcija radi isto što i photocamera(), ali za video mod.

 

photocamera()
 
app_lock.wait()

 

Jednostavno pokrećemo aplikaciju kamere u fotografskom modu i kažemo skripti da ne završi izvršavanje dok se desni meki taster (softkey) ne pritisne (i onda se poziva quit() funkcija).

 

 

8.6. Text to speech

Funkcija say() audio modula se može koristiti za izgovor Unicode teksta fonetski (u trenutnom jeziku uređaja). Pomoću ovog postaje trivijalno jednostavno dodati tekst govornim mogućnostima naše aplikacije. Sledeći primer ‘reprodukuje’ tekst ‘Python on Symbian’:

 

import audio
 
audio.say(u"Python on Symbian")

 

 

8.7. Snimanje i reprodukovanje zvučnih fajlova

Audio zapis se može snimiti i reprodukovati korišćenjem audio modula. Najčešće korišteni metodi zvučnih objekata su:

 

Podržani formati zvučnih fajlova variraju u zavisnosti od uređaja. Sledeći isečak koda demonstrira kako snimiti zvučni zapis i onda ga reprodukovati.

 

import appuifw, e32, audio
 
app_lock = e32.Ao_lock()
 
def quit():
  #Zatvori Sound objekat
  s.close()
  app_lock.signal()
appuifw.app.exit_key_handler = quit
 
file_path = u"C:\\Data\\my_sound_file.mp3"
 
#Otvori zvučni fajl
s = audio.Sound.open(file_path)
 
#Snimaj 10 sekundi
s.record()
e32.ao_sleep(10)
s.stop()
 
#Reprodukuj beskrajno
s.play(audio.KMdaRepeatForever)
 
app_lock.wait()

 

 

8.8. PyMPlayer – primer muzičkog player-a

Sledeći primer je osnovna verzija aplikacije muzičkog plejera. Njegove osobine uključuju play/pause funkcionalnost, kod za pojačanje i smanjenje glasnoće zvuka, i informacije za prikaz statusa reprodukovanja. Implementacija pause-e i resume-a su vredne pomena, s obzirom da ne postoje standardne metode za njih.

 

import appuifw, e32, audio, graphics, os, sysinfo
from key_codes import *
 
 
app_lock = e32.Ao_lock()
def quit():
  app_lock.signal()
appuifw.app.exit_key_handler = quit
 
class PyMPlayer:
  def __init__(self, path):

        #Putanja gde će player tražiti zvučne fajlove
        self.path = path

        #Promenljiva koja vodi računa o trenutnom stanju player-a;
        #None znači da nijedna pesma nije otvorena, False znači da je reprodukovanje pauzirano, a True             #znači da se pesma reprodukuje
        self.playing = None
       
        #Ovo ćemo koristiti kada se vraćamo iz pauze, kako bismo znali odakle treba nastaviti reprodukciju
        self.pickup_time = 0

        #Kreiraj sliku veličine ekrana
        self.img = graphics.Image.new(sysinfo.display_pixels())
       
        #Inicijalizuj Canvas i podesi ga kao telo aplikacije,
        #i veži (bind) tastere koji kontrolišu opcije reprodukovanja
        self.canvas = appuifw.Canvas(redraw_callback=self.handle_redraw)
        self.canvas.bind(EKeyUpArrow, self.volume_up)
        self.canvas.bind(EKeyDownArrow, self.volume_down)
        self.canvas.bind(EKeySelect, self.play_pause)
        appuifw.app.body = self.canvas
        appuifw.app.menu = [(u"Pick song", self.select_song)]
       
        #Inicijalizuj tajmer koji će se koristiti za ažuriranje informacija koje se prikazuju na ekranu
        self.timer = e32.Ao_timer()
 
  def handle_redraw(self, rect):
        self.canvas.blit(self.img)
 
      #Ova funkcija nalazi sve zvučne fajlove u određenom direktorijumu   
      #i prikazuje ih u listi da bi korisnik mogao da izabere
  def select_song(self):
        self.list_of_songs = []
        for self.i in os.listdir(self.path):
              
               #Proveri da li je ekstenzija od zvučnog fajla, ako jeste, dodaj ime na listu
               if self.i[-4:] in [".mp3", ".wav", ".wma"]:
                      self.list_of_songs.append(unicode(self.i))

               #Pitaj korisnika koji fajl želi reprodukovati, ako ih uopšte ima
        if len(self.list_of_songs) > 0:
               try:
                      self.file_name = self.list_of_songs[appuifw.selection_list(self.list_of_songs)]
               except TypeError: #Desi se kada korisnik poništi selekciju
                      pass
        else:
               appuifw.note(u"No appropriate sound files available", 'info')
 
  def volume_up(self):
        try:
               self.s.set_volume(self.s.current_volume() + 1)
        except:
               pass
 
  def volume_down(self):
        try:
               self.s.set_volume(self.s.current_volume() - 1)
        except:
               pass
 
  #Ovde rukujemo otvaranjem pesme po prvi put i reprodukcijom/pauziranjem iste
  def play_pause(self):
        if self.playing == False:
               self.s.set_position(self.pickup_time)
               self.playing = True
               self.s.play()
               self.show_info()
        elif self.playing == True:
               self.pickup_time = self.s.current_position()
               self.playing = False
               self.s.stop()
               self.timer.cancel()
        if self.playing == None:
               try: #Ako se play upotrebi pre odabira pesme, dolazi do greške
                      self.s = audio.Sound.open(self.path + "\\" + self.file_name)
                      appuifw.app.menu = [(u"Stop", self.stop)]
                      self.playing = True
                      self.s.play()
                      self.show_info()
               except:
                      pass
 
  #Ova funkcija rukuje zaustavljanjem reprodukcije
  def stop(self):
        appuifw.app.menu = [(u"Pick song", self.select_song)]
        self.s.stop()
        self.s.close()
        self.playing = None
        self.timer.cancel()
 
  #Funkcija koja utvrđuje i prikazuje proteklo vreme
  #i trajanje pesme, kao i ime fajla
  def show_info(self):
        #Izračunaj vrednosti
        self.min1, self.sec1 = divmod((self.s.current_position() / 1000000), 60)
        self.min2, self.sec2 = divmod((self.s.duration() / 1000000), 60)
        #Daj vrednostima standardni format mm:ss
        if self.min1<10:
               self.info = u"0" + unicode(self.min1)
        else:
               self.info = unicode(self.min1)
        self.info += u":"
        if self.sec1<10:
               self.info += u"0" + unicode(self.sec1)
        else:
               self.info += unicode(self.sec1)
        self.info += u" - "
        if self.min2<10:
               self.info += u"0" + unicode(self.min2)
        else:
               self.info += unicode(self.min2)
        self.info += u":"
        if self.sec2<10:
               self.info += u"0" + unicode(self.sec2)
        else:
               self.info += unicode(self.sec2)
        #Skloni sliku da se stvari ne bi preklapale
        self.img.clear()
        #Proračunaj gde da se prikažu informacije, tako da budu na centru ekrana
        self.text_size = self.img.measure_text(self.file_name, font='title')[0]
        self.text_width = self.text_size[2] - self.text_size[0]
        self.text_height = self.text_size[3] - self.text_size[1]
        self.text_x = (sysinfo.display_pixels()[0] / 2) - (self.text_width / 2)
        self.text_y = (sysinfo.display_pixels()[1] / 2) - self.text_height
        self.img.text((self.text_x, self.text_y), self.file_name, font='title')
        self.text_size = self.img.measure_text(self.info, font='title')[0]
        self.text_width = self.text_size[2] - self.text_size[0]
        self.text_height = self.text_size[3] - self.text_size[1]
        self.text_x = (sysinfo.display_pixels()[0] / 2) - (self.text_width / 2)
        self.text_y = (sysinfo.display_pixels()[1] / 2) - self.text_height
        self.img.text((self.text_x, self.text_y + self.text_height), self.info, font='title')
        self.handle_redraw(())
        #Ažuriraj informacije svake sekunde
        self.timer.after(1, self.show_info)
 
  #Destruktor klase; ovde zatvaramo trenutno otvoreni zvučni fajl, alp ga o,a
  def __del__(self):
        try:
               self.s.stop()
               self.s.close()
        except:
               pass
 
  #Kreiraj instancu player-a, presleđujući mu putanju zvučnih fajlova kao argument
  player = PyMPlayer(u"C:\\Data")
 
  app_lock.wait()

 

Pri prvom pogledu na ovaj primer koda, primećujemo da je muzički plejer implementiran kao klasa, a ne kao serija funkcija. Ovaj pristup omogućuje plejeru da bude korišten od strane drugih aplikacija jednostavnim import-ovanje istog kao i bilo koje druge klase iz modula.

 

  def __init__(self, path):

        #Putanja gde će player tražiti zvučne fajlove
        self.path = path

        #Promenljiva koja vodi računa o trenutnom stanju player-a;
        #None znači da nijedna pesma nije otvorena, False znači da je reprodukovanje pauzirano, a True             #znači da se pesma reprodukuje
        self.playing = None
       
        #Ovo ćemo koristiti kada se vraćamo iz pauze, kako bismo znali odakle treba nastaviti reprodukciju
        self.pickup_time = 0

        #Kreiraj sliku veličine ekrana
        self.img = graphics.Image.new(sysinfo.display_pixels())
       
        #Inicijalizuj Canvas i podesi ga kao telo aplikacije,
        #i veži (bind) tastere koji kontrolišu opcije reprodukovanja
        self.canvas = appuifw.Canvas(redraw_callback=self.handle_redraw)
        self.canvas.bind(EKeyUpArrow, self.volume_up)
        self.canvas.bind(EKeyDownArrow, self.volume_down)
        self.canvas.bind(EKeySelect, self.play_pause)
        appuifw.app.body = self.canvas
        appuifw.app.menu = [(u"Pick song", self.select_song)]
       
       #Inicijalizuj tajmer koji će se koristiti za ažuriranje informacija koje se prikazuju na ekranu
        self.timer = e32.Ao_timer()

 

Ovo je konstruktor. Podaci koji su potrebni odmah nakon pokretanja plejera su definisani ovde. Trenutni zvučni objekat, međutim, je definisan u play_pause() metodu nakon što je zvučni fajl izabran.

 

  #Ova funkcija nalazi sve zvučne fajlove u određenom direktorijumu   
  #i prikazuje ih u listi da bi korisnik mogao da izabere
  def select_song(self):
        self.list_of_songs = []
        for self.i in os.listdir(self.path):
              
               #Proveri da li je ekstenzija od zvučnog fajla, ako jeste, dodaj ime na listu
               if self.i[-4:] in [".mp3", ".wav", ".wma"]:
                      self.list_of_songs.append(unicode(self.i))

        #Pitaj korisnika koji fajl želi reprodukovati, ako ih uopšte ima
        if len(self.list_of_songs) > 0:
               try:
                      self.file_name = self.list_of_songs[appuifw.selection_list(self.list_of_songs)]
               except TypeError: #Desi se kada korisnik poništi selekciju
                      pass
        else:
               appuifw.note(u"No appropriate sound files available", 'info')
 

 

Ovu funkciju koristimo za odabir pesme koju želimo reprodukovati. Ona pretražuje direktorijum na određenoj putanji za fajlove čije su ekstenzije zvučnih fajlova i, kada ih nađe, dodaje ih na listu. Lista se onda prikazuje pomoću selekcione liste, korisnik izabere fajl, i onda se utvrđuje ime fajla pesme koja će se reprodukovati.

 

  def volume_up(self):
        try:
               self.s.set_volume(self.s.current_volume() + 1)
        except:
               pass
 
  def volume_down(self):
        try:
               self.s.set_volume(self.s.current_volume() - 1)
        except:
               pass

 

Prethodne dve funkcije rukuju sa promenom glasnoće reprodukcije. One se pozivaju kada se pritisnu tasteri up arrow i down arrow (strelica gore i strelica dole). Glasnoća (volume) se pojačava/stišava za jedan stepen, dok ne dostigne maksimum/0. Izuzeci koji se mogu javiti pri pokušaju da se podesi glasnoća iznad maksimuma ili ispod minimuma ili kada se podešava glasnoća kada nije definisan zvučni objekat (set_volume() metod pripada zvučnom objektu) se rukuju sa try...except izjavama.

 

  def play_pause(self):
        if self.playing == False:
               self.s.set_position(self.pickup_time)
               self.playing = True
               self.s.play()
               self.show_info()
        elif self.playing == True:
               self.pickup_time = self.s.current_position()
               self.playing = False
               self.s.stop()
               self.timer.cancel()
        if self.playing == None:
               try: #Ako se play upotrebi pre odabira pesme, dolazi do greške
                      self.s = audio.Sound.open(self.path + "\\" + self.file_name)
                      appuifw.app.menu = [(u"Stop", self.stop)]
                      self.playing = True
                      self.s.play()
                      self.show_info()
               except:
                      pass

 

Plejer može biti u jednom od tri stanja u svakom trenutku:

 

Funkcija play_pause() rukuje sa prelazima između ova tri stanja. Kada se pauzira reprodukcija, broju mikrosekundi koje su već reprodukovane se pristupa preko current_position() metoda i taj broj se skladišti u self.pickup.time, i reprodukovanje se zaustavlja. Kako bismo nastavili reprodukovanje, naznačujemo odakle bi fajl trebao započeti reprodukciju koristeći set_position() metod i reprodukcija počinje. Ovaj pristup se koristi zato što nema predefinisanih načina za pauziranje i nastavak reprodukcije.

 

  def stop(self):
        appuifw.app.menu = [(u"Pick song", self.select_song)]
        self.s.stop()
        self.s.close()
        self.playing = None
        self.timer.cancel()

 

Kao što samo ime sugeriše, ova funkcija se koristi da se zaustavi reprodukcija. Zvučni fajl se zatvori, meni se podesi tako da dopušta korisniku odabir druge pesme, i tajmer koji se koristi za ažuriranje informacija na ekranu se poništava (ažuriranje informacija kada se nijedna pesma reprodukuje bi bilo besmisleno).

 

  def show_info(self):
        #Izračunaj vrednosti
        self.min1, self.sec1 = divmod((self.s.current_position() / 1000000), 60)
        self.min2, self.sec2 = divmod((self.s.duration() / 1000000), 60)
        #Daj vrednostima standardni format mm:ss
        if self.min1<10:
               self.info = u"0" + unicode(self.min1)
        else:
               self.info = unicode(self.min1)
        self.info += u":"
        if self.sec1<10:
               self.info += u"0" + unicode(self.sec1)
        else:
               self.info += unicode(self.sec1)
        self.info += u" - "
        if self.min2<10:
               self.info += u"0" + unicode(self.min2)
        else:
               self.info += unicode(self.min2)
        self.info += u":"
        if self.sec2<10:
               self.info += u"0" + unicode(self.sec2)
        else:
               self.info += unicode(self.sec2)
        #Skloni sliku da se stvari ne bi preklapale
        self.img.clear()
        #Proračunaj gde da se prikažu informacije, tako da budu na centru ekrana
        self.text_size = self.img.measure_text(self.file_name, font='title')[0]
        self.text_width = self.text_size[2] - self.text_size[0]
        self.text_height = self.text_size[3] - self.text_size[1]
        self.text_x = (sysinfo.display_pixels()[0] / 2) - (self.text_width / 2)
        self.text_y = (sysinfo.display_pixels()[1] / 2) - self.text_height
        self.img.text((self.text_x, self.text_y), self.file_name, font='title')
        self.text_size = self.img.measure_text(self.info, font='title')[0]
        self.text_width = self.text_size[2] - self.text_size[0]
        self.text_height = self.text_size[3] - self.text_size[1]
        self.text_x = (sysinfo.display_pixels()[0] / 2) - (self.text_width / 2)
        self.text_y = (sysinfo.display_pixels()[1] / 2) - self.text_height
        self.img.text((self.text_x, self.text_y + self.text_height), self.info, font='title')
        self.handle_redraw(())
        #Ažuriraj informacije svake sekunde
        self.timer.after(1, self.show_info)

 

Ovde jednostavno izračunavamo vreme trajanja pesme i koliko je već prošlo tokom reprodukcije i prikazujemo tu informaciju uz ime fajla. Ova funkcija se poziva svake sekunde uz pomoć tajmerovog after() metoda.


Metod measure_text() se koristi da se odredi koliko prostora (u pikselima) će određeni tekst zauzeti ukoliko se ispiše određenim font-om. Vraćeni rezultat sadrži 4-elementni tuple sa koordinatama gornjeg-levog i donjeg-desnog ugla pravougaonika koji bi sadržao tekst. Ovu informaciju koristimo da odredimo gde da iscrtamo tekst kako bi on bio prikazan u sredini ekrana.

 

  def __del__(self):
        try:
               self.s.stop()
               self.s.close()
        except:
               pass

 

Metod __del__ objekta se poziva kada će taj objekat biti uništen. Ovde zatvaramo bilo koji otvoreni zvučni fajl.

 

 

 

PyS60