diff --git a/README.md b/README.md
index f598b9b..cbeebe8 100644
--- a/README.md
+++ b/README.md
@@ -20,51 +20,58 @@ pip install -r requirements.txt
## USAGE ##
-tnmake [-h] -i path [-o filename] [-q percentage] [-w px] [-c annotation] [-g layout] [-e {bmp,jpg,png}] [-f path] [-v] [-V]
+usage: tnmake -i path [-o path] [-w px] [-c annotation] [--grid layout] [-e {bmp,jpg,png}] [-q x] [-f path] [-s px]
-* -h, --help
+Thumbnail!MAKER creates thumbnails with some additional information
- show this help message and exit
+options:
+ -h, --help
-* -i path, --input path
+ * show this help message and exit
-set video file path
+ -i path, --input path
-* -o filename, --output filename
+ * set video filepath
-set custom output filename
+ -o path, --output path
-* -q percentage, --quality percentage
+ * force a custom output filepath
-set output image quality (default: 100)
+ -w px, --width px
-* -w px, --width px
+ * set width of the output image
-set width of output image
+ --comment annotation
-* -c annotation, --comment annotation
+ * append a thumbnail annotation
-add a comment as a thumbnail annotation
+ --grid layout
-* -g layout, --grid layout
+ * set layout of the output thumbnail
-set layout of a resulting thumbnail
+ -e {bmp,jpg,png}, --extension {bmp,jpg,png}
-* -e {bmp,jpg,png}, --extension {bmp,jpg,png}
+ * choose the output extension (default: "jpg")
-choose the output extension (default: "jpg")
+ -q x, --quality x
-* -f path, --font path
+ * set quality, affects lossy image formats only
-set path to a fontfile (default: "Mono Input Condensed (Light, Italic)")
+ -f path, --font path
-* -v, --verbose
+ * select an available font or pass a path to a desired fontfile
-enable verbose mode
+ -s px, --size px
-* -V, --version
+ * set desired fontsize (default: 13px)
-show program's version number and exit
+ -v, --verbose
+
+ * enable verbose mode
+
+ -V, --version
+
+ * show program's version number and exit
### EXAMPLES ###
@@ -73,30 +80,11 @@ show program's version number and exit
tnmake -i "Blade Runner (1982) Original.mkv" -w 750 -c "This is useful for adding small annotations (such as text labels)"
```
-
-Example 1: Edgar Wallace (1964) Der Hexer
-
-![Edgar Wallace (1964) Der Hexer](images/v0.2.0-example.0.jpg)
-
-
-
-Example 2: Babylon 5 (S01E01)
-
-![Babylon 5 (S01E01)](images/v0.2.0-example.1.jpg)
-
-
-
-Example 3: Blade Runner (1982) Original
-
-![Blade Runner (1982) Original](images/v0.2.0-example.2.jpg)
-
-
-
## CHANGELOG ##
### Thumbnail!MAKER 0.2.0, updated @ 2022-11-03 ###
-* add a `pip` installable package
+* pack the project into a `pip` package ([visit](https://pypi.org/project/tnmake/))
### Thumbnail!MAKER 0.1.1, updated @ 2020-01-14 ###
diff --git a/images/v0.2.0-example.0.jpg b/images/v0.2.0-example.0.jpg
deleted file mode 100644
index 25a957e..0000000
Binary files a/images/v0.2.0-example.0.jpg and /dev/null differ
diff --git a/images/v0.2.0-example.1.jpg b/images/v0.2.0-example.1.jpg
deleted file mode 100644
index d9c1ca5..0000000
Binary files a/images/v0.2.0-example.1.jpg and /dev/null differ
diff --git a/images/v0.2.0-example.2.jpg b/images/v0.2.0-example.2.jpg
deleted file mode 100644
index a8ab7d5..0000000
Binary files a/images/v0.2.0-example.2.jpg and /dev/null differ
diff --git a/samples/v0.2.0-example.0.jpg b/samples/v0.2.0-example.0.jpg
new file mode 100644
index 0000000..80c0c83
Binary files /dev/null and b/samples/v0.2.0-example.0.jpg differ
diff --git a/samples/v0.2.0-example.1.jpg b/samples/v0.2.0-example.1.jpg
new file mode 100644
index 0000000..0dda51d
Binary files /dev/null and b/samples/v0.2.0-example.1.jpg differ
diff --git a/samples/v0.2.0-example.2.jpg b/samples/v0.2.0-example.2.jpg
new file mode 100644
index 0000000..3e3b47e
Binary files /dev/null and b/samples/v0.2.0-example.2.jpg differ
diff --git a/samples/v0.2.0-example.3.jpg b/samples/v0.2.0-example.3.jpg
new file mode 100644
index 0000000..001a882
Binary files /dev/null and b/samples/v0.2.0-example.3.jpg differ
diff --git a/samples/v0.2.0-example.4.jpg b/samples/v0.2.0-example.4.jpg
new file mode 100644
index 0000000..9b7b56f
Binary files /dev/null and b/samples/v0.2.0-example.4.jpg differ
diff --git a/setup.py b/setup.py
index 13d82df..f272459 100644
--- a/setup.py
+++ b/setup.py
@@ -1,33 +1,40 @@
from setuptools import setup, find_packages
-setup(
- name='tnmake', # Thumbnail!MAKER
- version='0.2.0',
- license='GPLv3+',
- description='Thumbnail!MAKER creates for you customisable thumbnails with some additional tech information',
- package_dir={'':'src'},
- packages=find_packages(where='src'),
- author='nschepsen',
- author_email='schepsen@web.de',
- url='https://github.com/nschepsen/thumbnail-maker',
- keywords='library, video, tools',
- entry_points={
+# load the project description
+
+with open('README.md', 'r') as fh:
+
+ ld = fh.read()
+
+# set up the pip pkg installer
+
+setup(name = 'tnmake', # Thumbnail!MAKER
+ version = '0.2.0',
+ license = 'GPLv3+',
+ description = 'Thumbnail!MAKER creates customisable thumbnails',
+ package_dir = {'':'src'},
+ packages = find_packages(where='src'),
+ install_requires = ['python-iso639', 'python-rapidjson>=1.6'],
+ author = 'nschepsen',
+ author_email = 'nikolaj@schepsen.eu',
+ # github
+ keywords = 'library, video, tools',
+ # module's metadata
+ entry_points = {
'console_scripts': ['tnmake=tnmake.main:main']},
- classifiers=[
- 'Development Status :: 4 - Beta'
+ classifiers = [
+ 'Development Status :: 4 - Beta',
'Environment :: Console',
'Intended Audience :: End Users/Desktop',
- 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)'
- 'Natural Language :: English',
- 'Natural Language :: German',
- 'Natural Language :: Italian',
- 'Natural Language :: Russian',
+ 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
'Operating System :: POSIX :: Linux',
'Programming Language :: Python :: 3',
- 'Topic :: Desktop Environment',
'Topic :: Home Automation',
- 'Topic :: Multimedia :: Video :: Display', 'Topic :: Utilities'
- ]
+ 'Topic :: Multimedia :: Video', 'Topic :: Utilities'
+ ],
+ long_description = ld, # long description
+ long_description_content_type = 'text/markdown',
+ url = 'https://github.com/nschepsen/thumbnail-maker' # repo
)
-# Thumbnail!MAKER creates customisable thumbnails and adds some tech details in the picture's header
\ No newline at end of file
+# Thumbnail!MAKER creates customisable thumbnails (ffmpeg wrapper)
\ No newline at end of file
diff --git a/src/tnmake/ColaborateLight.otf b/src/tnmake/ColaborateLight.otf
deleted file mode 100644
index 9f68f9b..0000000
Binary files a/src/tnmake/ColaborateLight.otf and /dev/null differ
diff --git a/src/tnmake/InputMonoCondensedLightItalic.ttf b/src/tnmake/InputMonoCondensedLightItalic.ttf
deleted file mode 100644
index 62cd352..0000000
Binary files a/src/tnmake/InputMonoCondensedLightItalic.ttf and /dev/null differ
diff --git a/src/tnmake/main.py b/src/tnmake/main.py
index 0cee785..dcccbf6 100644
--- a/src/tnmake/main.py
+++ b/src/tnmake/main.py
@@ -27,68 +27,74 @@ def main():
Here is the ENTRYPOINT for `pip` and `standalone` versions
'''
- parser = ArgumentParser(description=f'{__project__} creates customisable thumbnails with some additional information')
+ parser = ArgumentParser(description = f'{__project__} creates thumbnails with some additional information')
# add cli arguments
parser.add_argument(
'-i', '--input',
- required=True, # pass a filepath
- help=f'set video file path ',
- metavar='path',
- type=apt_path_exists) # movie
+ required = True,
+ help = f'set video filepath',
+ metavar = 'path',
+ type = apt_path_exists)
parser.add_argument(
'-o', '--output',
- help='set custom output filename',
- metavar='filename')
+ help = 'force a custom output filepath',
+ metavar = 'path')
# type=abspath,
- # default=getcwd()) # use a current working directory as default
- parser.add_argument(
- '-q', '--quality',
- type=int,
- help='set output image quality (default: 100)',
- metavar='percentage',
- default=100) # quality gets over the space usage
+ # default=getcwd()) # use the current working directory as default
parser.add_argument(
'-w', '--width',
- type=int,
- help='set width of output image',
- metavar='px',
- default=1150)
+ type = int,
+ help = 'set width of the output image',
+ metavar = 'px',
+ default = 1150)
parser.add_argument(
- '-c', '--comment',
- type=str,
- help='add a comment as a thumbnail annotation',
- metavar='annotation',
- default='') # let it empty if you won't left any commments
+ '--comment',
+ type = str,
+ help = 'append a thumbnail annotation',
+ metavar = 'annotation',
+ default = '') # let it empty if you won't append any commment
parser.add_argument(
- '-g', '--grid',
- type=str,
- help='set layout of a resulting thumbnail',
- metavar='layout',
- default='3x8') # predefined layout consists of 3 cols & 8 rows
+ '--grid',
+ type = str,
+ help = 'set layout of the output thumbnail',
+ metavar = 'layout',
+ default = '3x8') # predefined layout consists of 3 cols & 8 rows
parser.add_argument(
'-e', '--extension',
- help='choose the output extension (default: "jpg")',
- choices=['bmp', 'jpg', 'png'],
- default='jpg')
+ help = 'choose the output extension (default: "jpg")',
+ choices = ['bmp', 'jpg', 'png'],
+ default = 'jpg')
+ parser.add_argument(
+ '-q', '--quality',
+ type = int,
+ help = 'set quality, affects lossy image formats only',
+ metavar = 'x',
+ default = 100) # quality gets over the space usage
parser.add_argument(
'-f', '--font',
- type=str,
- help='set path to a fontfile (default: "Mono Input Condensed (Light, Italic)")',
- metavar='path')
+ type = str,
+ help = 'select an available font or pass a path to a desired fontfile',
+ metavar = 'path')
+ parser.add_argument(
+ '-s', '--size',
+ type = int,
+ help = 'set desired fontsize (default: 13px)',
+ metavar = 'px')
parser.add_argument(
'-v', '--verbose',
- action='store_true',
- default=False,
- help='enable verbose mode')
+ action = 'store_true',
+ default = False,
+ help = 'enable verbose mode')
parser.add_argument(
'-V', '--version',
- action='version',
- version=f'{__project__} v{__version__}')
+ action = 'version',
+ version = f'{__project__} v{__version__}')
args = parser.parse_args() # evaluate all arguments and pass 'em through
if args.verbose:
logger.setLevel(10)
options = {
'font': args.font, # set defaults in class itself
+ 'size': args.size,
'layout': args.grid, # 3x8
'comment': args.comment,
'ext': args.extension,
diff --git a/src/tnmake/tnmake.py b/src/tnmake/tnmake.py
index 485a385..e8a2d07 100755
--- a/src/tnmake/tnmake.py
+++ b/src/tnmake/tnmake.py
@@ -51,21 +51,19 @@ def __init__(self, options: dict):
self.options = {
'comment': options.get('comment').strip(),
'extension': options.get('ext', 'jpg'),
- 'font': options.get('font') if isfile(str(options.get('font', 'd'))) else join(self.fpath, 'InputMonoCondensedLightItalic.ttf'),
- 'fontsize': 13,
+ 'font': options.get('font') or '', # default system font
+ 'fontsize': options.get('size') or 13,
'grid': options.get('layout', f'3x8'),
'quality': options.get('quality', 100),
- 'tfont': join(self.fpath, 'ColaborateLight.otf'),
+ # 'tfont': join(self.fpath, 'ColabLig.otf'), # Personal Use Only
'width': options.get('width', 1150)
}
# DBG # -----------------------------------------------------------------------
- for k, v in self.options.items():
- # dump it
- logger.debug(
- f'"{k}": {v if k not in ["font", "tfont"] else v.split(chr(47))[-1]}')
+ for k, v in self.options.items(): # dump options
+ logger.debug(f'"{k}": {v}') # for debug purposes
# --- # -----------------------------------------------------------------------
- def perform(self, video: str, output=None):
+ def perform(self, video: str, output = None):
filename, ext = (lambda t: (t[0], t[1][1:]))(splitext(basename(video)))
metadata = loads(
@@ -102,6 +100,7 @@ def perform(self, video: str, output=None):
bitrate = str(floordiv(int(bitrate), 1000))
# handle video codec type specific tags
if stream['codec_type'] == 'video':
+ self.options['pointsize'] = truediv(stream['height'], 4.4)
# try to get video resolution
resolution = f'{stream["width"]}x{stream["height"]}'
# try to get display aspect ratio, e.g. 16:9
@@ -131,11 +130,13 @@ def perform(self, video: str, output=None):
for s in streams[:-1]:
for x in s:
logger.debug(f'"{x}"')
- logger.debug(f'"SubTitles: {", ".join(streams[2])}"')
+ logger.debug(f'"Subtitles: {", ".join(streams[2]) or "Not Present"}"')
# --- # -----------------------------------------------------------------------
# PART 2: Snapshot Taking
shots = (lambda x: x[0]*x[1])(list(map(int, self.options['grid'].split('x'))))
framelist = []
+ if not output or output.endswith(chr(47)):
+ output = f'{output or ""}{filename}'
thumbnail = join(getcwd(), f'{output or filename}.bmp')
for i in range(shots):
step = floordiv(duration, shots)
@@ -145,37 +146,37 @@ def perform(self, video: str, output=None):
# append a frame id to the frame list
framelist.append(join(getcwd(), f'frame{frame:06d}.bmp'))
call(['ffmpeg', '-ss', str(frame), '-v', 'error', '-i', video, '-vframes', '1', framelist[-1], '-y'])
- call(['convert',
- framelist[-1],
- '-font', self.options.get('tfont'),
- # '-undercolor', 'white',
- '-pointsize', '200',
+ call(['convert', framelist[-1],
+ # '-undercolor', 'white',
+ '-pointsize', f'{self.options["pointsize"]:.0f}',
'-gravity', 'South',
- '-stroke', 'rgba(0,0,0,0.50)',
- # '-strokewidth', '1',
+ '-stroke', 'rgba(0,0,0,0.750)',
+ # '-strokewidth', '1',
'-fill', 'rgba(255,255,255,0.20)',
'-annotate', '+0+30', f'{str(timedelta(seconds=frame)).zfill(8)}',
framelist[-1]])
# concate screenshots to a pre-defined grid layout
- call(['montage', '-tile', self.options.get('grid'), '-geometry', '+2+2', *framelist, thumbnail])
+ call(['montage', '-tile', self.options.get('grid'), '-geometry', '+3+3', *framelist, thumbnail])
# adjust the thumbnail width to a value you prefere
call(['mogrify', '-resize', str(self.options.get('width')), thumbnail])
# create an annotation containing tech information
n, st_string = len(streams[StreamType.SubTitle]), 'Not Present'
if n:
- st_string = ', '.join(streams[StreamType.SubTitle][:3]) + (f' and {n - 3} more' if n > 3 else '')
+ st_string = ', '.join(streams[2][:3]) + (f' and {n - 3} more' if n > 3 else '')
annotation = (
f'Filename: {filename}.{ext}\n'
f'{chr(10).join([i for j in streams[:2] for i in j])}\n' # video & audio streams
f'Duration: {str(timedelta(seconds=duration)).zfill(8)}, ' # hh:mm:ss
f'Size: {filesize:,} Bytes ({hrunits(filesize)})'
f'{chr(10) if len(streams[StreamType.SubTitle]) > 2 else ", "}'
- f'SubTitles: {st_string}'
+ f'Subtitles: {st_string}'
f'{chr(10) + "Comment: " + self.options.get("comment") if self.options.get("comment") else ""}')
# render annotation string ('annotation.bmp')
- call(['convert',
- '-font', self.options.get('font'),
- '-density', '288',
+ convert = ['convert']
+ if self.options.get('font'):
+ convert.extend(['-font', self.options.get('font')])
+ call([*convert,
+ '-density', '300', # 288
'-resize', '25%',
'-pointsize', str(self.options.get('fontsize')),
f'label:{annotation}',