commit 6f394cc6110dd24180eb1a647bdf3fc4b9f06d6c Author: Xenofon Konitsas Date: Sun Nov 22 04:41:52 2020 +0200 Initial commit diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5391d87 --- /dev/null +++ b/.gitignore @@ -0,0 +1,138 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..add6748 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020 Xenofon Konitsas + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..40d8794 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include README.md +include images/logo.png \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..616bdc7 --- /dev/null +++ b/README.md @@ -0,0 +1,129 @@ + +

+ + Logo + + +

tkVideo

+ +

+ Python module for playing videos (without sound) inside tkinter Label widget using Pillow and imageio +
+ +

+ +

+ + Contributors + + + Forks + + + Stargazers + + + Issues + + + MIT License + +

+ + + + + + +## About The Project + +tkVideo is a Python module for playing videos in GUIs created with tkinter. It does so by binding to a `tkinter.Label` widget of the user's choice and rapidly changing its image object. + + +### Built With + +* [tkinter (Python built-in)](https://docs.python.org/3/library/tkinter.html) +* [imageio](imageio.github.io) +* [Pillow](https://pypi.org/project/Pillow/) + + +## Installation + +### End-users: + + * Clone the repo and run `setup.py` +```sh +git clone https://github.com/huskeee/tkvideo.git +python ./tkvideo/setup.py +``` +or + * Install the package from PyPI +```sh +pip install tkvideo +``` + +### Developers and contributors + * Clone the repo and install the module in developer mode +```sh +git clone https://github.com/huskeee/tkvideo.git +python ./tkvideo/setup.py develop +``` +or + * Install the package from PyPI in editable mode +```sh +pip install -e tkvideo +``` + +This will create a shim between your code and the module binaries that gets updated every time you change your code. + + + +## Usage + +* Import tkinter and tkvideo +* Create `Tk()` parent and the label you'd like to use +* Create `tkvideo.tkvideo` object with its parameters (video file path, label name, whether to loop the video or not and size of the video) +* Start the player thread with `.play()` +* Start the Tk main loop + +Example code: +```py +from tkinter import * +from tkvideo import tkvideo + +root = Tk() +my_label = Label(root) +my_label.pack() +player = tkvideo.tkvideo("C:\\path\\to\\video.mp4", my_label, loop = 1, size = (1280,720)) +player.play() + +root.mainloop() +``` + +## Issues / suggestions + +Have a problem that needs to be solved or a suggestion to make? See the [issues](https://github.com/huskeee/tkvideo/issues) page. + + +## License + +Distributed under the MIT License. See `LICENSE` for more information. + + + +## Contact + +Xenofon Konitsas - [@huskeeeeee](https://twitter.com/huskeeeeee) - konitsasx@gmail.com + +Project Link: [https://github.com/huskeee/tkvideo](https://github.com/huskeee/tkvideo) + + +## Special thanks to + +* [@Pythonista](https://stackoverflow.com/users/5230901/pythonista) on StackOverflow for the frame loading code + + + + + +##### Readme file created using [Othneil Drew's awesome template ♥](https://github.com/othneildrew/Best-README-Template) \ No newline at end of file diff --git a/images/logo.png b/images/logo.png new file mode 100644 index 0000000..9da67c2 Binary files /dev/null and b/images/logo.png differ diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..5cd78e2 --- /dev/null +++ b/setup.py @@ -0,0 +1,30 @@ +from setuptools import setup + +with open('README.md') as f: + readme = f.read() + +setup(name = 'tkvideo', + version = '0.1.0', + description = 'Python module for playing videos (without sound) inside tkinter Label widget using Pillow and imageio', + long_description = readme, + long_description_content_type = "text/markdown", + classifiers = [ + 'Development Status :: 4 - Beta', + 'License :: OSI Approved :: MIT License', + 'Programming Language :: Python :: 3.9', + 'Topic :: Multimedia :: Video :: Display' + ], + keywords = 'tkvideo tkinter video display label pillow imageio huskee', + url = 'https://github.com/huskeee/tkvideo', + author = 'Xenofon Konitsas (huskee)', + author_email = 'konitsasx@gmail.com', + license = 'MIT', + packages = ['tkvideo'], + install_requires = [ + 'imageio', + 'imageio-ffmpeg', + 'pillow' + ], + include_package_data = True, + zip_safe = False +) diff --git a/tkvideo/__init__.py b/tkvideo/__init__.py new file mode 100644 index 0000000..5b1337d --- /dev/null +++ b/tkvideo/__init__.py @@ -0,0 +1 @@ +from .tkvideo import tkvideo \ No newline at end of file diff --git a/tkvideo/tkvideo.py b/tkvideo/tkvideo.py new file mode 100644 index 0000000..38db79b --- /dev/null +++ b/tkvideo/tkvideo.py @@ -0,0 +1,66 @@ +""" tkVideo: Python module for playing videos (without sound) inside tkinter Label widget using Pillow and imageio + +Copyright © 2020 Xenofon Konitsas +Released under the terms of the MIT license (https://opensource.org/licenses/MIT) as described in LICENSE.md + +""" + +try: + import Tkinter as tk # for Python2 (although it has already reached EOL) +except ImportError: + import tkinter as tk # for Python3 +import threading +import imageio +from PIL import Image, ImageTk + +class tkvideo(): + """ + Main class of tkVideo. Handles loading and playing + the video inside the selected label. + + :keyword path: + Path of video file + :keyword label: + Name of label that will house the player + :param loop: + If equal to 0, the video only plays once, + if not it plays in an infinite loop (default 0) + :param size: + Changes the video's dimensions (2-tuple, + default is 640x360) + + """ + def __init__(self, path, label, loop = 0, size = (640,360)): + self.path = path + self.label = label + self.loop = loop + self.size = size + + def load(self, path, label, loop): + """ + Loads the video's frames recursively onto the selected label widget's image parameter. + Loop parameter controls whether the function will run in an infinite loop + or once. + """ + frame_data = imageio.get_reader(path) + + if loop == 1: + while True: + for image in frame_data.iter_data(): + frame_image = ImageTk.PhotoImage(Image.fromarray(image).resize(self.size)) + label.config(image=frame_image) + label.image = frame_image + else: + for image in frame_data.iter_data(): + frame_image = ImageTk.PhotoImage(Image.fromarray(image).resize(self.size)) + label.config(image=frame_image) + label.image = frame_image + + def play(self): + """ + Creates and starts a thread as a daemon that plays the video by rapidly going through + the video's frames. + """ + thread = threading.Thread(target=self.load, args=(self.path, self.label, self.loop)) + thread.daemon = 1 + thread.start()