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}',