Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
Photo album slideshow
-
Eyy, that full screen option works great!
Video is proving tricky though. All my videos play directly from the photos app, but many don’t play through Pythonista. I’ve found a few other video player implementations on the forum, they all seem to behave the same.
When I print the video paths to the console, I’ve noticed a pattern. Videos that play have these shorter paths, and tend to be older (like filmed at least 4 years ago) :
/var/mobile/Media/DCIM/101APPLE/IMG_1684.MOV
/var/mobile/Media/DCIM/101APPLE/IMG_1821.MOVWhile newer ones look like this, and won’t play:
/var/mobile/Media/PhotoData/CPLAssets/group286/9683CE80-D66B-488B-B3C0-BCDBA800F11D.MOV
/var/mobile/Media/PhotoData/CPLAssets/group1/DF32BCF7-E210-406F-99A9-FDCC843117C1.MP4
/var/mobile/Media/PhotoData/CPLAssets/group21/3A29108B-320A-43E8-87BD-08A5CDEDAC11.MOVIs that a known issue?
-
@mestela said
/var/mobile/Media/PhotoData/CPLAssets/group....
A quick search via Google seems to say that if you find this kind of path, that will say that your asset is on iCloud and you only see a thumbnail. Could you try to force download on your device by playing it in the Photos app. And then, use Pythonista script. Anyway, I don't know this issue
And, if you use the first script, does it show all videos?
-
Hmm, all the videos play in the photos album fine. The original script didn’t play any of the videos either.
If there’s no known solution, maybe I can try copying the videos to a new album, see if that’s enough to make them ‘local’ and not iCloud based, if that’ll make the playback system happier.
-
@mestela said
The original script didn’t play any of the videos either.
Weird. For me, on IPad iOS 16.3.1 with Pythonista beta 340008, it works but my photos/vidéos are not on iCloud.
-
@mestela said
maybe I can try copying the videos to a new album
I can't understand , if your photos are synchronized with iCloud, why this copy could change something.
-
Seemed to work for this person:
https://developer.apple.com/forums/thread/676918
When I checked the asset url's, I noticed that if the video has been retrieved from iCloud, its filename has a ".medium" postfix like in this example:
file:///var/mobile/Media/PhotoData/Metadata/PhotoData/CPLAssets/group338/191B2348-5E19-4A8E-B15C-A843F9F7B5A3.medium.MP4
The weird thing is, if I use FileManager to copy the video in this url to another directory, create a new AVAsset from that file, and use that asset when creating the AVExportSession instance, the problem goes away.
No guarantees of course, but will give it a go over the weekend.
-
Worked on the rest of it, so fade up/fade down of the images, and a tap will skip to the next one.
Still lots wrong with it (timer needs to reset when you tap, needs a subtle progress bar, play/pause controls, needs a ui to choose the album, code layout is terrible, etc), but again, its now behaving more like I want a photo slideshow to run. Loving pythonista!
import ui import itertools import photos import time import random class MyView (ui.View): # get image assets albums = photos.get_albums() album = [x for x in albums if x.title == 'Tvstream'][0] assets = album.assets random.shuffle(assets) assets = [x for x in assets if x.media_type == 'image'] print('total assets: ' + str(len(assets))) i = 0 asset = assets[i] def draw(self): img_view.image=self.asset.get_ui_image() def touch_ended(self, touch): # Called when a touch begins. self.i += 1 self.asset = self.assets[self.i] print('asset is now: '+str(self.asset)) self.set_needs_display() def touch_moved(self, touch): # Called when a touch moves. pass def touch_began(self, touch): # Called when a touch ends. pass v = MyView() v.background_color = 'black' w,h = ui.get_screen_size() v.frame = (0,0,w,h) img_view = ui.ImageView() img_view.frame = v.frame img_view.content_mode = ui.CONTENT_SCALE_ASPECT_FIT img_view.image=v.asset.get_ui_image() v.add_subview(img_view) v.present('fullscreen',hide_title_bar=True) for asset in itertools.cycle(v.assets): start_time = time.time() current_time = start_time hold_time = 15 end_time = start_time + hold_time # interruptable timer iv = img_view while current_time < end_time: if iv.alpha < 1.0 and current_time-start_time<1.0: iv.alpha += 0.1 if end_time - current_time < 1.0: iv.alpha -= 0.1 time.sleep(0.05) current_time+=0.05 v.i += 1 v.asset = v.assets[v.i] v.set_needs_display()
-
@mestela If you want that the iOS status bar (hour/date/wifi/battery...) does not override your photo, please add this method to your class.
As your iPad is "older", I hope that its iOS is compatible with this code.
from objc_util import * . . . class MyView (ui.View): . . . def layout(self): insets = self.objc_instance.safeAreaInsets() self.frame = self.frame.inset(insets.top, insets.left, insets.bottom, insets.right)
-
eyy that works great, thanks!
update, now using the itertools.cycle properly so it runs endlessly without halting, the timer is properly reset on click, few other little things:
import ui import itertools import photos from pprint import pprint import time import random from objc_util import * class MyView (ui.View): hold_time = 15 albums = photos.get_albums() album = [x for x in albums if x.title == 'Tvstream'][0] assets = album.assets assets = [x for x in assets if x.media_type == 'image'] random.shuffle(assets) print('total assets: ' + str(len(assets))) assets = itertools.cycle(assets) asset = next(assets) start_time = time.time() current_time = start_time end_time = start_time + hold_time def layout(self): insets = self.objc_instance.safeAreaInsets() self.frame = self.frame.inset(insets.top, insets.left, insets.bottom, insets.right) def reset_timer(self): self.start_time = time.time() self.current_time = self.start_time self.end_time = self.start_time + self.hold_time def draw(self): iv.image=self.asset.get_ui_image() def touch_ended(self, touch): # Called when a touch begins. self.asset = next(self.assets) print('asset is now: '+str(self.asset)) self.reset_timer() self.set_needs_display() def touch_moved(self, touch): # Called when a touch moves. pass def touch_began(self, touch): # Called when a touch ends. pass v = MyView() v.background_color = 'black' w,h = ui.get_screen_size() v.frame = (0,0,w,h) iv = ui.ImageView() iv.frame = v.frame iv.content_mode = ui.CONTENT_SCALE_ASPECT_FIT iv.alpha=0.0 iv.image=v.asset.get_ui_image() v.add_subview(iv) v.reset_timer() v.present('fullscreen',hide_title_bar=True) v.asset = next(v.assets) while 1: # interuptable timer iv.alpha = 0.0 while v.current_time < v.end_time: if iv.alpha < 1.0 and v.current_time-v.start_time<1.0: iv.alpha += 0.1 if v.end_time - v.current_time < 1.0: iv.alpha -= 0.1 time.sleep(0.05) v.current_time+=0.05 v.reset_timer() v.asset = next(v.assets) v.set_needs_display()
-
insets = self.objc_instance.safeAreaInsets()
self.frame = self.frame.inset(insets.top, insets.left, insets.bottom, insets.right)Does this work?
self.frame = self.frame.inset(*self.objc_instance.safeAreaInsets())
-
@ccc no
self.frame = self.frame.inset(*self.objc_instance.safeAreaInsets())
TypeError: Rect.inset() argument after * must be an iterable, not UIEdgeInsets
-
Nice, thanks!
Funny that often when I lookup how to implement the next feature I'm interested in, I also find a way to simplify code I've written. The gestures module looks perfect for handling swipe events, in finding that I also discovered I should be using ui.animate and ui.delay. Too many cool things!
I'm also pleased that this redesign seems to be more stable; if I left the previous script run overnight, in the morning pythonista had inevitably crashed. This morning I came in to see the ipad was still happily showing images. :)
-
@mestela said
The gestures module looks perfect for handling swipe events
Not only it looks perfect, but it is perfect. I use it very often.
-
Yep, too easy. Now I have swipe working, but that in turn meant I had to replace the itertools endless cycle with a deque. Calling this done for now.
import ui import itertools import photos from pprint import pprint import time import random from objc_util import * import gestures from collections import deque class MyView (ui.View): hold_time = 15 albums = photos.get_albums() album = [x for x in albums if x.title == 'Tvstream'][0] assets = album.assets assets = [x for x in assets if x.media_type == 'image'] random.shuffle(assets) #assets = assets[-5:] print('total assets: ' + str(len(assets))) assets = deque(assets) asset = assets[0] start_time = time.time() current_time = start_time end_time = start_time + hold_time def layout(self): insets = self.objc_instance.safeAreaInsets() self.frame = self.frame.inset(insets.top, insets.left, insets.bottom, insets.right) def reset_timer(self): self.start_time = time.time() self.current_time = self.start_time self.end_time = self.start_time + self.hold_time def draw(self): iv.image=self.asset.get_ui_image() v = MyView() v.background_color = 'black' w,h = ui.get_screen_size() v.frame = (0,0,w,h) iv = ui.ImageView() iv.frame = v.frame iv.content_mode = ui.CONTENT_SCALE_ASPECT_FIT iv.alpha=0.0 iv.image=v.asset.get_ui_image() v.add_subview(iv) v.present('fullscreen',hide_title_bar=True) def swipe_left(data): v.assets.rotate(-1) v.asset = v.assets[0] v.reset_timer() v.set_needs_display() def swipe_right(data): v.assets.rotate(1) v.asset = v.assets[0] v.reset_timer() v.set_needs_display() gestures.swipe(v, swipe_left, direction=gestures.LEFT) gestures.swipe(v, swipe_right, direction=gestures.RIGHT) while 1: # interuptable timer iv.alpha = 0.0 while v.current_time < v.end_time: if iv.alpha < 1.0 and v.current_time-v.start_time<1.0: iv.alpha += 0.1 if v.end_time - v.current_time < 1.0: iv.alpha -= 0.1 time.sleep(0.05) v.current_time+=0.05 v.assets.rotate(-1) v.asset = v.assets[0] v.reset_timer() v.set_needs_display()
-
@mestela you can move two lines in your reset_timer() method
def reset_timer(self): self.asset = v.assets[0] # <------- self.start_time = time.time() self.current_time = self.start_time self.end_time = self.start_time + self.hold_time self.set_needs_display() # <-------
def swipe_left(data): v.assets.rotate(-1) #v.asset = v.assets[0] v.reset_timer() #v.set_needs_display() def swipe_right(data): v.assets.rotate(1) #v.asset = v.assets[0] v.reset_timer() #v.set_needs_display() . . . while 1: . . v.assets.rotate(-1) #v.asset = v.assets[0] v.reset_timer() #v.set_needs_display()
-
@mestela and even do the rotate in your reset_timer() method, passing the rotate argument
def reset_timer(self,rot): self.assets.rotate(rot) .
def swipe_left(data): #v.assets.rotate(-1) #v.asset = v.assets[0] v.reset_timer(-1) <------------ #v.set_needs_display()
-
@mestela It would be better to use ui.animate for your alpha animation, but it is not interruptible, "as is"
But thanks to ObjectiveC, you can cancel a view animation, knowing that, doing so, goes directly at end of the animation. Not sure that I'm clear enough, thus this little example to show effect.
Without canceling the animation, the ImageView see its alpha going from 0 to 1 in 10 seconds.
Pressing the stop animation button, directly goes to alpha = 1import ui from objc_util import * v = ui.View(background_color='white') v.frame = (0,0,400,400) iv = ui.ImageView() iv.frame = (10,10,300,300) iv.image = ui.Image.named('test:Lenna') iv.alpha = 0 v.add_subview(iv) bstop = ui.Button(title='stop\nanimation') bstop.border_width = 1 bstop.border_color ='blue' bstop.corner_radius = 10 # display button title in multiple lines bo = bstop.objc_instance for sv in bo.subviews(): if hasattr(sv,'titleLabel'): tl = sv.titleLabel() tl.numberOfLines = 0 break bstop.frame = (310,10,80,50) def stopanim(sender): iv.objc_instance.layer().removeAllAnimations() bstop.action = stopanim v.add_subview(bstop) v.present('sheet') def animation(): iv.alpha = 1.0 # fade in ui.animate(animation, duration=10.0)
-
Great stuff, thanks! All these little tips are great for getting my rusty python skills back online.
I used to do a lot more python in my main 3d animation software, but when I switched apps, the new one doesn't require as much python, so I haven't really touched python properly in about 8 years.
-
Worked on the rest of it, so fade up/fade down of the images, and a tap will skip to the next one.
-
This post is deleted!