Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- from kivy.app import App
- from kivy.uix.boxlayout import BoxLayout
- from kivy.uix.gridlayout import GridLayout
- from kivy.uix.scrollview import ScrollView
- from kivy.uix.label import Label
- from kivy.uix.textinput import TextInput
- from kivy.uix.button import Button
- from kivy.uix.image import Image
- from kivy.core.image import Image as CoreImage
- from kivy.core.window import Window
- from kivy.uix.popup import Popup
- from kivy.uix.scatter import Scatter
- from kivy.uix.progressbar import ProgressBar
- from kivy.clock import Clock
- from kivy.uix.scatterlayout import ScatterPlane
- from kivy.graphics.transformation import Matrix
- from kivy.vector import Vector
- import requests
- import base64
- import io
- import json
- from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
- from cryptography.hazmat.backends import default_backend
- from cryptography.hazmat.primitives import padding
- import hashlib
- import threading
- import queue
- class ZoomableImage(ScatterPlane):
- def __init__(self, texture, **kwargs):
- super(ZoomableImage, self).__init__(**kwargs)
- self.texture = texture
- self.do_rotation = False
- self.do_scale = True
- self.do_translation = True
- # Create the image widget
- self.image = Image(texture=texture, size=texture.size)
- self.add_widget(self.image)
- # Set up initial state
- self.original_size = texture.size
- self.fit_image_to_screen()
- # Set up zoom limits
- self.min_zoom = self.scale
- self.max_zoom = 3.0 # Allow zooming in up to 3x the original size
- def fit_image_to_screen(self):
- # Calculate the scale to fit the image within the window
- scale_x = Window.width / self.original_size[0]
- scale_y = Window.height / self.original_size[1]
- self.scale = min(scale_x, scale_y) * 0.9 # 90% of fit scale for padding
- # Center the image
- self.center = Window.center
- def on_touch_down(self, touch):
- if touch.is_double_tap:
- self.fit_image_to_screen()
- return True
- return super(ZoomableImage, self).on_touch_down(touch)
- def on_touch_move(self, touch):
- if len(self._touches) >= 2:
- # Use two fingers to zoom
- touch1, touch2 = self._touches[:2]
- old_distance = Vector(touch1.px - touch2.px, touch1.py - touch2.py).length()
- new_distance = Vector(touch1.x - touch2.x, touch1.y - touch2.y).length()
- if old_distance > 0 and new_distance > 0:
- scale = new_distance / old_distance
- new_scale = scale * self.scale
- if self.min_zoom <= new_scale <= self.max_zoom:
- self.scale = new_scale
- self.center = touch.pos
- else:
- # Use one finger to pan
- super(ZoomableImage, self).on_touch_move(touch)
- class ImageViewer(BoxLayout):
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- self.orientation = 'vertical'
- self.gallery_url = None
- self.password = None
- self.images = []
- self.encrypted = False
- self.album_title = ""
- self.image_queue = queue.Queue()
- self.input_layout = BoxLayout(orientation='vertical', size_hint=(1, None), height=200)
- self.add_widget(self.input_layout)
- self.input_layout.add_widget(Label(text='Enter Gallery URL or ID:'))
- self.url_input = TextInput(multiline=False)
- self.input_layout.add_widget(self.url_input)
- self.input_layout.add_widget(Label(text='Enter password (leave empty if none):'))
- self.password_input = TextInput(multiline=False, password=True)
- self.input_layout.add_widget(self.password_input)
- self.submit_button = Button(text='Submit')
- self.submit_button.bind(on_press=self.on_submit)
- self.input_layout.add_widget(self.submit_button)
- self.progress_bar = ProgressBar(max=100, size_hint=(1, None), height=40)
- self.add_widget(self.progress_bar)
- self.scroll_view = ScrollView(size_hint=(1, None), size=(Window.width, Window.height - 240))
- self.add_widget(self.scroll_view)
- self.image_grid = GridLayout(cols=3, spacing=10, size_hint_y=None)
- self.image_grid.bind(minimum_height=self.image_grid.setter('height'))
- self.scroll_view.add_widget(self.image_grid)
- def on_submit(self, instance):
- self.gallery_url = self.process_url(self.url_input.text)
- self.password = self.password_input.text
- self.load_gallery_info()
- def process_url(self, url):
- if url.startswith('http://') or url.startswith('https://'):
- paste_id = url.split('/')[-1]
- return f'https://paste.ee/r/{paste_id}'
- else:
- return f'https://paste.ee/r/{url}'
- def decrypt_data(self, encrypted_data):
- try:
- encrypted_data = base64.b64decode(encrypted_data)
- iv = encrypted_data[:16]
- encrypted_data = encrypted_data[16:]
- key = hashlib.sha256(self.password.encode()).digest()
- cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
- decryptor = cipher.decryptor()
- decrypted_data = decryptor.update(encrypted_data) + decryptor.finalize()
- unpadder = padding.PKCS7(128).unpadder()
- unpadded_data = unpadder.update(decrypted_data) + unpadder.finalize()
- return unpadded_data.decode()
- except Exception as e:
- print(f"Decryption error: {e}")
- return None
- def verify_password(self, encrypted_hash):
- if not encrypted_hash:
- return False
- decrypted_hash = self.decrypt_data(encrypted_hash)
- if decrypted_hash is None:
- return False
- salt = base64.b64decode(decrypted_hash)[:16]
- stored_hash = base64.b64decode(decrypted_hash)[16:]
- computed_hash = hashlib.sha256(salt + self.password.encode()).digest()
- return computed_hash == stored_hash
- def load_gallery_info(self):
- try:
- response = requests.get(self.gallery_url)
- gallery_info = json.loads(response.text)
- self.encrypted = gallery_info.get('encrypted', False)
- if self.encrypted:
- password_hash = gallery_info.get('password_hash')
- if not password_hash or not self.verify_password(password_hash):
- self.show_error_message("Incorrect password or missing password hash. Unable to decrypt gallery.")
- return
- decrypted_title = self.decrypt_data(gallery_info['album_title'])
- self.album_title = decrypted_title if decrypted_title else gallery_info['album_title']
- else:
- self.album_title = gallery_info['album_title']
- self.images = gallery_info['image_list']
- # Start loading images in a separate thread
- threading.Thread(target=self.load_images).start()
- # Start processing the queue in the main thread
- Clock.schedule_interval(self.process_image_queue, 0.1)
- except requests.exceptions.RequestException:
- self.show_error_message("Network error. Please check your connection and try again.")
- except json.JSONDecodeError:
- self.show_error_message("Error decoding gallery information. Please check the URL.")
- def load_images(self):
- total_images = len(self.images)
- for i, url in enumerate(self.images):
- full_url = self.process_url(url)
- try:
- response = requests.get(full_url)
- image_data = response.text
- if self.encrypted and self.password:
- decrypted_data = self.decrypt_data(image_data)
- image_data = decrypted_data if decrypted_data else image_data
- decoded_image = base64.b64decode(image_data)
- # Put the decoded image data in the queue
- self.image_queue.put((decoded_image, i, total_images))
- except Exception as e:
- print(f"Error loading image: {e}")
- Clock.schedule_once(lambda dt: self.show_error_message(f"Error loading image: {str(e)}"))
- def process_image_queue(self, dt):
- try:
- while True:
- decoded_image, i, total_images = self.image_queue.get_nowait()
- # Load image data into memory
- buf = io.BytesIO(decoded_image)
- coreimage = CoreImage(buf, ext='png')
- # Create Image widget from CoreImage
- image = Image(texture=coreimage.texture, size_hint=(1, None), height=200)
- image.bind(on_touch_down=self.on_image_touch)
- self.image_grid.add_widget(image)
- # Update progress bar
- progress = (i + 1) / total_images * 100
- self.progress_bar.value = progress
- if i + 1 == total_images:
- # All images loaded, stop the scheduled interval
- Clock.unschedule(self.process_image_queue)
- # Hide progress bar when done
- self.progress_bar.opacity = 0
- except queue.Empty:
- pass
- def on_image_touch(self, instance, touch):
- if instance.collide_point(*touch.pos):
- content = ZoomableImage(instance.texture)
- popup = Popup(title=self.album_title, content=content, size_hint=(0.9, 0.9))
- # Bind to window size to update image position and scale when orientation changes
- Window.bind(on_resize=lambda instance, width, height: content.fit_image_to_screen())
- popup.open()
- def show_error_message(self, message):
- popup = Popup(title='Error', content=Label(text=message), size_hint=(0.8, 0.4))
- popup.open()
- class GalleryViewerApp(App):
- def build(self):
- return ImageViewer()
- if __name__ == '__main__':
- GalleryViewerApp().run()
Add Comment
Please, Sign In to add comment