chidea / FBpyGIF

Pure Python implemented Frame Buffer memory mapping library with GIF animation playing support on SBC(Raspberry pi)
MIT License
34 stars 5 forks source link

looks promising. i will try, but does it support animated gifs? #1

Closed ozett closed 8 years ago

ozett commented 8 years ago

is there a limitation to the gif format? does it support animated gifs?

thanks

chidea commented 8 years ago

Well, the limitation there is not something you'd be care of. Any GIF file that can be opened with PILlow is supported. This project is FOR the animated gif, obviously it supports them.

chidea commented 8 years ago

http://pillow.readthedocs.io/en/3.2.x/handbook/image-file-formats.html Here says it supports both 87a and 89a.

ozett commented 8 years ago

cool. i tried, but... have a raspberry pi2, fresh raspian from today (2016-05-27-raspbian-jessie.img), cloned github, tried to run fb.py...but errors

root@raspberrypi:/usr/src/FBpyGIF# ./fb.py from: can't read /var/mail/mmap from: can't read /var/mail/ioctl ./fb.py: Zeile 139: import: Kommando nicht gefunden. ./fb.py: Zeile 141: mm: Kommando nicht gefunden. ./fb.py: Zeile 142: bpp,: Kommando nicht gefunden. ./fb.py: Zeile 143: vi,: Kommando nicht gefunden. ./fb.py: Zeile 144: _fb_cmap: Kommando nicht gefunden. ./fb.py: Zeile 146: Syntaxfehler beim unerwarteten Wort (' ./fb.py: Zeile 146:def ready_fb(_bpp = 24, i = 0):'

installed python-pil on default python 2.7, but still this errors.... what to do? thaaaanks..

ozett commented 8 years ago

tried as user "pi", but...

pi@raspberrypi:/usr/src/FBpyGIF $ python fb.py Traceback (most recent call last): File "fb.py", line 138, in from ioctl import * ImportError: No module named ioctl pi@raspberrypi:/usr/src/FBpyGIF $

chidea commented 8 years ago

It's made for py3. Try with sudo python3 fb.py test.gif. Writing to fb driver needs super user privileges.

ozett commented 8 years ago

cool. fastest reply ever. thhaaaanks. i will try. (may you add this at prominent place to the readme.md here?)

chidea commented 8 years ago

Okay, I will. Thanks for feedbacks.

ozett commented 8 years ago

ohhh. whats wrong... python

ozett commented 8 years ago

tried with gif on cmd-line, still ioctl error ..

python2

ozett commented 8 years ago

Pil missing for python3 ...just a guess...?

chidea commented 8 years ago

It has to be from fcntl import * instead of from ioctl import *

ozett commented 8 years ago

i changed it. no error in terminal. looks better. you update github-code? i go check on my screen on the raspberry in another room. thanks so far :smile:

chidea commented 8 years ago

Yup, did it right now. Thanks for noticing.

ozett commented 8 years ago

my plan did not work... i wanted to build a picture frame with 1980x1020, so i rotated my raspberry screen with 90 degrees. i quickly tried two of my gifs, but i did not worked... i assumed i would be succesfull, even if i rotated the screen ... or am i wrong ..?

python3

chidea commented 8 years ago

You mean, FHD screen in vertical with 1080*1920 pixels resolution? I never tried it by my self but in ready_fb function, there's two ioctl commands with FBIOGET_VSCREENINFO and FBIOPUT_VSCREENINFO flags which tries to get screen info and set back after some modifications. You could try to force set its resolution settings to what you want by vi[:4] = 1080, 1920, 1080, 1920.

ozett commented 8 years ago

yes. rotated FHD 90 degree in /boot/config.txt

where do i set this options? cmd-line? or inside fb.py?

chidea commented 8 years ago

It's inside the fb.py, line around 158.

chidea commented 8 years ago

Wait a minute.. I guess your image is not having the same resolution as screen. There'e a way to force resizing it.. I'll push it soon.

ozett commented 8 years ago

cool...maybe autoresize to width/height... if youre on this... :smiley:

chidea commented 8 years ago

I've pushed the auto resizing version but it seems to be working in horizontal mode only. Rotating with pillow function .rotate(90) would do the work by now..

chidea commented 8 years ago

I'll add auto rotating by reading angle info from ioctl. Please wait more..

chidea commented 8 years ago

It looks like the frame buffer driver doesn't know if the screen is rotated or not. I'll try other ways..

chidea commented 8 years ago

Oh.. I found how to do it. Will push the code soon.

chidea commented 8 years ago

Updated the code. Done a test in rotated mode. 640d3adc135977b26f57b7c05b2d55616d26b7e3

ozett commented 8 years ago

Fantastic! i will post a picture in a minute... meanwhile you coded i tried different ways to achieve a picture frame...

tried "mirage" (with autosize) on raspberry with x, tried fbi (no animated gif) on FB, googled, rotated my 800x480 panel..

but now i looks awesome on the 1920x1080 display in 90 degree rotation and autosize...!

:santa: does it load all images from a directory... ??? :santa:

FANTASTIC! thanks!

ozett commented 8 years ago

python4

found the image here (animated gif, the eyes blink from time to time): http://headlikeanorange.tumblr.com/archive

chidea commented 8 years ago

Happy for you! After doing the same things, made this script. That's why I think it's the only program suitable for an animated raspberry pi kiosk. You can use os.path python default library for that. I'll update that too.

chidea commented 8 years ago

4abb93c5abb3c9eeeacd6773e1b153a8dd13d82f This update will scan a directory and play all images under it. sudo python3 fb.py [directory or file] [delay seconds for static images=30] sudo python3 fb.py will just clear screen.

ozett commented 8 years ago

i had to look at the picture quality, the resizing is fantastic good. i had a comparison to other viewers, but the 500x700 animated gifs looks amazing good on 1080x1920. Realy Great!

i will give your update another try (ofter my first coffee).. 1) i thought, if i can mix the animated gifs with some .jpegs from my family-album? do you want support (or is it already in?) some other file fomats for a expanded picture frame? 2) it suits my needs for autoresize and auto-center, but what if another one wants original-size and displaying at left-corner/right-corner... :smile_cat:


(by the way, i tried "info-beamer" as a kiosk-displayer on raspberry with my rtsp-cams. it is promising..)

chidea commented 8 years ago

It does already support other file formats including jpg, png, bmp. Full list is again, the same as the PILlow library has. You'll be surprised how many this library supports. Some other mature features are not in the scope of this project.. This project is providing a library, not an program. Which means it's only showing an way how it can be done. Much complex features like playlist, network synchronization, user configuration, head-up displays are what I've done in a bigger close source project. You can take this sources and try to build it by yourself at no cost, at anytime.

ozett commented 8 years ago

ok, i will see how far i can come with this, as i am no developer..

a the moment i tried to give the directory option with a 20 second delay. i have a mix of animated and static gifs in the directory. the static gifs stay 20 seconds, the animated gifs show only 2-3 sek. animation, than the gif changed to the next. is this the default, or something what comes from the mixtures of static/animation and is intended?

chidea commented 8 years ago

Everyone becomes coding hobbyist in such way... :) It simply reads delay information of each frame from the gif file itself by cut.info['duration']. Which means that 2-3 seconds are just as the gif artist expected to be as the entire playtime.

ozett commented 8 years ago

ok, i understand.... may "you" want at last make the x-second delay for static images a global delay (also looping the gifs)? if you intend not to change the code again, i will see if i understand anything of you python to do i as a bloody hobbiist :sweat_smile: anyway, the library is super! :thumbsup:

chidea commented 8 years ago

I deeply understand that this program is yet the only way to do things like this. I'm simply too busy to make other "crazier" things.. When I have some free time, I'll consider to add such features. What you've asked needs several steps of works and quite personal need it is.. Thus I'll give you a snippet, instead of an updating.

...
  from threading import Event, Timer
  e=Event()
  try:
    for fpath in cycle(fpaths):
      if fpath[-4:].lower() == '.gif':
        Timer(DELAY, lambda e:e.set(), [e]).start()
        gif_loop(ready_img(fpath), e, True)
        e.clear()
      else:
...

Yeah, gif_loop function was already in ready for this work.

ozett commented 8 years ago

ok, nevertheless thanks for all help. great! :satisfied: i'll have a look after a strong coffee..and try to fiddle around... :boom:

ozett commented 8 years ago

i am forced to look at it later at night, the kids love the animals looping ... python5-2

ozett commented 8 years ago

there is room to experiment.... :thought_balloon: i tried to overlay the animated gif with an rtsp-stream from my webcams. it worked

omxplayer --layer -2 --adev local -n 1 --win '370 50 722 248'--stats --vol 0 --video_queue 10 --timeout 30 rtsp://192.168.14.103:554/ch0_1.h264

now i can push an videostream over the pictureframe in case on motion-detection sensors or on the alarm-events from motion-detection on my grandstream videocams.

cooooooolllll :+1:

chidea commented 8 years ago

What an amazing father you are! Thanks for sharing your experiences. Yes you can mix up videos and web pages, sensors, etc. Welcome to the world of IoT. When I have time, I'll first try to reduce loading terms between gif animations. RGB_to_BGR is just taking up too many time to. Please feel free to do any experiment you want.

ozett commented 8 years ago

i was motivated with this wonderfull results to test it a bit more...

here you can (not really in motion) see, that there is an semitransparent letter-animation of info-beamer running over the animated gif with your python library, and also semitransparent on another layer in there the realtime movie-stream from my webcams (which is currently an experimentyl feature in info-beamer) all running on my 480x800 panel, means rotatet 90 degree .... FANTASTIC! (but lots of work to come to realize all ideas...) :laughing: python6

ozett commented 8 years ago

looping with bash not working...i am not experienced enough in python..need help?

i tried to overcome changing the fb.py by scripting a bash-loop:

#!/bin/bash
while true
do
    animation=$(/bin/ls /home/pi/Pictures/ | /usr/bin/shuf -n 1)
    echo "File: $animation"
    export animation=$animation

    /usr/bin/python3 /usr/src/FBpyGIF/fb.py "/home/pi/Picures/${animation}"

    sleep 200s
done

but the file-name not gets passed to the python script. i changed fb.py to debug, but the right count of args is passed from bash. and iserted this:

#debug args
  # Get the arguments list
  cmdargs = str(argv)

  # Print it
  #print ("The total numbers of args passed to the script: %d " % total)
  print ("Args list: %s " % cmdargs)

i must be something with the dir/filename recognition.. i am lost ..

it is all ok, if i do it in bash-cmd line.


root@raspberrypi:/home/pi# python3 /usr/src/FBpyGIF/fb.py /home/pi/Pictures/tumblr_nz385enEIC1rc3rlfo1_1280.jpg
Args list: ['/usr/src/FBpyGIF/fb.py', '/home/pi/Pictures/tumblr_nz385enEIC1rc3rlfo1_1280.jpg']
you can manually set delay for non-animated files by 2nd argument (seconds)
files to play: ['/home/pi/Pictures/tumblr_nz385enEIC1rc3rlfo1_1280.jpg']

and from the script:

root@raspberrypi:/home/pi# ./display.sh
File: tumblr_mmj673VFUL1r4zr2vo2_r2_500.gif
Args list: ['/usr/src/FBpyGIF/fb.py', '/home/pi/Picures/tumblr_mmj673VFUL1r4zr2vo2_r2_500.gif']
you can manually set delay for non-animated files by 2nd argument (seconds)
files to play: []
chidea commented 8 years ago

Oh my.. My code also has infinite loop code and it would be much easier to learn some python and modify that loop than using shell script.. Because the Python is a good replacement for shell scripts. for fpath in cycle(fpaths): makes infinite loop iterator with fpaths list. So.. why are you making it with bash again?

chidea commented 8 years ago

So.. you wanna shuffle around? How about changing for fpath in cycle(fpaths): to

from random import shuffle
while True:
  shuffle(fpaths)
  for fpath in fpaths:

You'll need to take care of other indents of lines below.

ozett commented 8 years ago

i now "must" dive into python...because i cannot make it work. my (horrible noob) debuging

root@raspberrypi:/home/pi# ./display.sh
File: tumblr_n0wh4dIl3i1r4zr2vo1_500.gif
Args list: ['/usr/src/FBpyGIF/fb.py', '/home/pi/Picures/tumblr_n0wh4dIl3i1r4zr2vo1_500.gif', '44']
args length: 3
file/dir:  /home/pi/Picures/tumblr_n0wh4dIl3i1r4zr2vo1_500.gif
debug: reclist started
debug.reclist: no dir/no file /home/pi/Picures/tumblr_n0wh4dIl3i1r4zr2vo1_500.gif
files to play: []

the check for "isdir(path/file)" in rec_list_dir function seems to work different from cmd-line than from bash-script. (i googled something about absoulte-path...??)

i will look up your hints for python on this rainy sunday... thanks a lot (up to now) :smile:

ozett commented 8 years ago

never give up on the looping animals... :smile_cat:

heavy to jump up from nothing to python threading... as far as i understand your code, the "gif_loop" loops endless for showing the animated gif. if i want this to stop after DELAY, i thing at the moment, i have to wrap this in an own event with timer. i have no experience in python coding, so i dont see another way... and ...i ....jump ...

python7

oho, heavy stuff: "tuple unpacking" for looping an animal: http://stackoverflow.com/questions/10867882/tuple-unpacking-in-for-loops :smiling_imp:

chidea commented 8 years ago

That e in loop is actually not doing anything. It's left over of other threading experiment. The code snippet I wrote for you is the only part needs to be changed for letting DELAY applied to every images. Already done a test that it works fine. Here's entire end of the working code.

def gif_loop(gif, event=None, force_loop=False):
  from threading import Thread, Event, Timer
  from itertools import cycle
  ready_fb()
  imgs = ready_gif(gif)
  for img, dur in cycle(imgs) if force_loop else imgs:
    e=Event()
    Timer(dur, lambda e:e.set(), [e]).start()
    show_img(img)
    e.wait() # wait for animation frame duration
    if event and event.is_set():
      break
  if event: event.clear()

def rec_list_dir(path):
  from os.path import isdir, isfile
  from imghdr import what
  if isdir(path):
    from os import listdir
    from os.path import join
    rst = []
    for f in listdir(path):
      rst += rec_list_dir(join(path, f))
    return rst
  elif isfile(path) and what(path):
    return [path]
  return []

if __name__ == '__main__':
  from sys import argv
  if len(argv) == 1:
    ready_fb()
    black_scr()
    exit()

  if len(argv)<3:
    DELAY=30
    print('you can manually set delay for non-animated files by 2nd argument (seconds)')
  else:
    DELAY=int(argv[2])
  fpaths = rec_list_dir(argv[1])
  print('files to play:', fpaths)
  from itertools import cycle
  from time import sleep
  from threading import Event, Timer
  e=Event()
  try:
    for fpath in cycle(fpaths):
      if fpath[-4:].lower() == '.gif':
        Timer(DELAY, lambda e:e.set(), [e]).start()
        gif_loop(ready_img(fpath), e, True)
        e.clear()
      else:
        ready_fb()
        show_img(RGB_to_BGR(ready_img(fpath).convert('RGB').resize((w,h))))
        sleep(DELAY)
  except KeyboardInterrupt:
    e.clear() # stop gif loop
  finally:
    #e.wait() # wait for thread end
    black_scr()

And ran sudo python3 fb.py gif to read all image files in gif directory.

ozett commented 8 years ago

again, i am thankful again. really. not only for the code, but for pushing me investigating the inner workings of python language. i start with tuples, threading and callbacks. i suprised myself, what i can google in one day :innocent: i thought of giving up during the day, but... it was interesting enough to stay on..

i had some ideas for later years, but do you considered (i did not really look it up in the code) to use layers? i can set info-beamer Frambuffer output on different layers, also omxplayer too. is it possible with your python-library? :see_no_evil:

thanks for the code, i will put in in my ugly testing-fragments tomorrow, to get it clean and fantastic again. :thumbsup:


sometimes i start with random images that way, but that is my past. no i am a developer :punch:

 python3 fb.py $(ls /home/pi/Pictures/*.gif | shuf -n 1)
chidea commented 8 years ago

But your code won't shuffle again after a loop..?! While what I suggested will do. Layers aren't something considered but you made me consider now.. It probably would be able to be done with another ioctl.. As soon as I finish my work, I'll keep track on them too..

ozett commented 8 years ago

yes, your right. my shell workaround only picked one gif by random. it was just for the record. the task had been to heavy, to become a great python-dev like you from out of nothing...:wink:

if you are interested, i can show my trigger for overlaying the picture frame. i use a simple cgi-bin listener for the build-in motion-detection of my grandstream videoconverter. the grandstream calls the cgi on motion, and i show with omxplayer an rtsp-stream as overlay. could all be build in one python, i guess. would be perfect elegant, but i have to learn python more..in holidays, maybe.

i stumbled again on my own mistake, may it could be helpful to check for python-version on start?

root@raspberrypi:/usr/src/FBpyGIF# ./fb.py
from: can't read /var/mail/mmap
from: can't read /var/mail/fcntl
./fb.py: Zeile 139: import: Kommando nicht gefunden.
./fb.py: Zeile 141: mm: Kommando nicht gefunden.
./fb.py: Zeile 142: bpp,: Kommando nicht gefunden.
./fb.py: Zeile 143: vi,: Kommando nicht gefunden.
./fb.py: Zeile 144: _fb_cmap: Kommando nicht gefunden.
./fb.py: Zeile 146: Syntaxfehler beim unerwarteten Wort `('
./fb.py: Zeile 146: `def ready_fb(_bpp = 24, i = 0):'
root@raspberrypi:/usr/src/FBpyGIF# python3 fb.py
Args list: ['fb.py']
args length: 1
root@raspberrypi:/usr/src/FBpyGIF#

img-type selection was also my fault... :smirk:

root@raspberrypi:/usr/src/FBpyGIF# python3 fb2.py /home/pi/Pictures/*.gif
Traceback (most recent call last):
  File "fb2.py", line 297, in <module>
    DELAY=int(argv[2])
ValueError: invalid literal for int() with base 10: '/home/pi/Pictures/tumblr_mh1h2nLnn01r4zr2vo1_500.gif'
root@raspberrypi:/usr/src/FBpyGIF# python3 fb2.py /home/pi/Pictures/*.gif 60
Traceback (most recent call last):
  File "fb2.py", line 297, in <module>
    DELAY=int(argv[2])
ValueError: invalid literal for int() with base 10: '/home/pi/Pictures/tumblr_mh1h2nLnn01r4zr2vo1_500.gif'
root@raspberrypi:/usr/src/FBpyGIF# python3 fb2.py /home/pi/Pictures/ 60

(by the way: i keep on listening here...all very promising...:blush:)

ozett commented 8 years ago

i used the same unchanged dir as before, with a mix of .jpg and animated.gifs, used your code, but got this error after the first .jpg showed correct:

Traceback (most recent call last):
  File "fb2.py", line 308, in <module>
    gif_loop(ready_img(fpath), e, True)
  File "fb2.py", line 262, in gif_loop
    imgs = ready_gif(gif)
NameError: name 'ready_gif' is not defined

means i cannot select *.gifs in the directory arg, and the shuffle needs diretories with only one filetype. i go again diving into python (day2)...:disappointed: (edit: i looked a little bit around, my guess is, the ready_gif routine was an addition and is missing?)

chidea commented 8 years ago

Oh right.. ready_gif was missing. Here it goes. I've been testing to parallelize converting process.. so this code is a little dirty.. Sorry about that.

def _ready_gif(cut):
  return RGB_to_BGR(cut.convert('RGB').resize((w,h))).tobytes(), cut.info['duration']/1000
def ready_gif(gif):
  from PIL import ImageSequence
  #from multiprocessing import Pool
  ready_fb()
  imgs = []
  for img in ImageSequence.Iterator(gif):
    imgs.append(_ready_gif(img))#.copy())
  #with Pool(4) as p:
    #imgs=list(p.map(_ready_gif, imgs))
  return imgs

Python version checking seems useful. I'll add it soon.