Skip to content

Commit

Permalink
Add support for video reencoding
Browse files Browse the repository at this point in the history
  • Loading branch information
Arkanosis committed May 19, 2018
1 parent b4f203e commit d26521b
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 40 deletions.
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# tifoto [![Version](https://img.shields.io/badge/version-v1.0.3--dev-orange.svg)](https://semver.org/spec/v2.0.0.html) [![License](http://img.shields.io/badge/license-ISC-blue.svg)](/LICENSE)
# tifoto [![Version](https://img.shields.io/badge/version-v1.1.0-orange.svg)](https://semver.org/spec/v2.0.0.html) [![License](http://img.shields.io/badge/license-ISC-blue.svg)](/LICENSE)

**tifoto** is a simple tool for photo mass-resizing for emails.

## Features

tifoto currently does the following:
* Re-compress using JPEG quality 70%.
* Resize the largest dimension to 3840 pixels.
* Store minified photos in /tmp/tifoto so that they are deleted on reboot.
* Re-compress photos using JPEG quality 70% and videos using Webm with VP9 for video and Opus for audio.
* Resize the largest dimension of photos to 3840 pixels and videos to 720p.
* Store minified photos and videos in /tmp/tifoto so that they are deleted on reboot.
* Use all available CPU cores on the computer to process several photos at the same time.

It is available in English and in French.
Expand All @@ -20,7 +20,7 @@ Run `sudo install.sh`.

## Usage

Run `tifoto <all photos to resize>` in a terminal or use the “Resize photos using tifoto” action in Dolphin's context menu.
Run `tifoto <all photos and videos to resize>` in a terminal or use the “Resize photos and videos using tifoto” action in Dolphin's context menu.

![Dolphin menu preview](https://raw.githubusercontent.com/Arkanosis/tifoto/master/screenshots/menu.png)

Expand All @@ -33,6 +33,7 @@ Run `tifoto <all photos to resize>` in a terminal or use the “Resize photos us
tifoto would like to add the following features:
* Support other graphical environments than KDE.
* Use Guetzli instead of libjpeg / libjpeg-turbo.
* Use AV1 instead of VP9.

## Contributing and reporting bugs

Expand Down
8 changes: 4 additions & 4 deletions install.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#! /bin/sh

# tifoto installer
# (C) 2017 Jérémie Roquet <jroquet@arkanosis.net>
# (C) 2017-2018 Jérémie Roquet <jroquet@arkanosis.net>
# Released under the ISC license
# https://github.com/Arkanosis/tifoto/

Expand All @@ -11,12 +11,12 @@ cp locale/fr/LC_MESSAGES/tifoto.mo /usr/share/locale/fr/LC_MESSAGES
cat > /usr/share/kservices5/ServiceMenus/tifoto.desktop <<EOF
[Desktop Entry]
Type=Service
ServiceTypes=KonqPopupMenu/Plugin,image/jpeg,inode/directory
ServiceTypes=KonqPopupMenu/Plugin,image/jpeg,video/mov,video/mp4,video/webm,inode/directory
Actions=tifoto;
[Desktop Action tifoto]
Name=Resize photos using tifoto
Name[fr]=Redimensionner les photos avec tifoto
Name=Resize photos and videos using tifoto
Name[fr]=Redimensionner les photos et les vidéos avec tifoto
Icon=file-zoom-out
Exec=tifoto %U
EOF
Binary file modified locale/fr/LC_MESSAGES/tifoto.mo
Binary file not shown.
16 changes: 8 additions & 8 deletions locale/fr/LC_MESSAGES/tifoto.po
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
# tifoto v1.0.2
# (C) 2017 Jérémie Roquet <jroquet@arkanosis.net>
# tifoto v1.1.0
# (C) 2017-2018 Jérémie Roquet <jroquet@arkanosis.net>
# Released under the ISC license
# https://github.com/Arkanosis/tifoto/
#
msgid ""
msgstr ""
"Project-Id-Version: 1.0.2\n"
"Project-Id-Version: 1.1.0\n"
"POT-Creation-Date: 2017-06-08 02:46+0200\n"
"PO-Revision-Date: 2017-06-08 02:46+0200\n"
"PO-Revision-Date: 2018-05-19 02:46+0200\n"
"Last-Translator: Jérémie Roquet <jroquet@arkanosis.net>\n"
"Language-Team: Jérémie Roquet <jroquet@arkanosis.net>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

msgid "Usage: {} <all photos to resize>"
msgstr "Utilisation: {} <toutes les photos à redimensionner>"
msgid "Usage: {} <all photos and videos to resize>"
msgstr "Utilisation: {} <toutes les photos et vidéos à redimensionner>"

msgid "Resizing…"
msgstr "Redimensionnement en cours…"

msgid "Resizing \"{}\"…"
msgstr "Redimensionnement en cours de \"{}\"…"

msgid "Resized photos were written to \"{}\" and will be deleted at reboot."
msgstr "Les photos redimensionnées ont été écrites dans \"{}\" et seront supprimées lors du prochain redémarrage."
msgid "Resized photos and videos were written to \"{}\" and will be deleted at reboot."
msgstr "Les photos et vidéos redimensionnées ont été écrites dans \"{}\" et seront supprimées lors du prochain redémarrage."
59 changes: 42 additions & 17 deletions tifoto
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
#! /usr/bin/env python3

# tifoto v1.0.3-dev
# (C) 2017 Jérémie Roquet <jroquet@arkanosis.net>
# tifoto v1.1.0
# (C) 2017-2018 Jérémie Roquet <jroquet@arkanosis.net>
# Released under the ISC license
# https://github.com/Arkanosis/tifoto/

__version__ = '1.0.3-dev'
__version__ = '1.1.0'

import gettext
import os
Expand All @@ -16,7 +16,11 @@ import sys
import threading

tifoto_dir = '/tmp/tifoto'
jpeg_file = re.compile(r'\.jpe?g$', re.IGNORECASE)
photo_file = re.compile(r'\.jpe?g$', re.IGNORECASE)
video_file = re.compile(r'\.(mov|mp4)$', re.IGNORECASE)

photo_increment = 1
video_increment = 1000 # a video is worth a thousand pictures

class Progress:

Expand All @@ -39,9 +43,9 @@ class Progress:
else:
print('\t{}'.format(step))

def end(self):
def end(self, increment):
with self.__lock:
self.__step += 1
self.__step += increment
if os.environ.get('DISPLAY'):
self.__update('Set', '', 'value', str(self.__step))

Expand All @@ -61,47 +65,68 @@ def show_message(message):
else:
print(message)

def resize_photos(args):
def resize_files(args):
photos = []
videos = []
for arg in args:
if os.path.isdir(arg):
for dirpath, __, filenames in os.walk(arg, followlinks=True):
for filename in filenames:
if jpeg_file.search(filename):
if photo_file.search(filename):
photos.append((dirpath, filename))
elif jpeg_file.search(arg):
elif video_file.search(filename):
videos.append((dirpath, filename))
elif photo_file.search(arg):
photos.append(os.path.split(arg))
elif video_file.search(arg):
videos.append(os.path.split(arg))

progress = Progress(len(photos))
progress = Progress(len(photos) + 2 * video_increment * len(videos))
os.makedirs(tifoto_dir, exist_ok=True)

lock = threading.Lock()

def worker():
def photo_worker():
while not progress.stopped():
with lock:
if not photos:
break
directory, photo = photos.pop(0)
progress.begin(photo)
dest = photo
dest = re.sub(photo_file, '.jpg', photo)
while os.path.exists(os.path.join(tifoto_dir, dest)):
dest = '_' + dest
subprocess.run(['convert', '-resize', '3840x3840', '-quality', '70%', os.path.join(directory, photo), os.path.join(tifoto_dir, dest)])
subprocess.run(['touch', '-r', os.path.join(directory, photo), os.path.join(tifoto_dir, dest)])
progress.end()
progress.end(photo_increment)

# Photo resizing is mono-threaded, so process photos in parallel to use all available threads
threads = []
for thread in os.sched_getaffinity(0):
worker_thread = threading.Thread(target=worker)
worker_thread = threading.Thread(target=photo_worker)
threads.append(worker_thread)
worker_thread.start()

for thread in threads:
thread.join()

# Video conversion is already multi-threaded, so process videos sequentially
while not progress.stopped():
if not videos:
break
directory, video = videos.pop(0)
progress.begin(video)
dest = re.sub(video_file, '.webm', video)
while os.path.exists(os.path.join(tifoto_dir, dest)):
dest = '_' + dest
subprocess.run(['ffmpeg', '-i', os.path.join(directory, video), '-c:v', 'libvpx-vp9', '-s', 'hd720', '-pass', '1', '-b:v', '1M', '-threads', '0', '-speed', '4', '-tile-columns', '6', '-frame-parallel', '1', '-an', '-f', 'webm', '-y', '/dev/null'])
progress.end(video_increment)
subprocess.run(['ffmpeg', '-i', os.path.join(directory, video), '-c:v', 'libvpx-vp9', '-s', 'hd720', '-pass', '2', '-b:v', '1M', '-threads', '0', '-speed', '1', '-tile-columns', '6', '-frame-parallel', '1', '-auto-alt-ref', '1', '-lag-in-frames', '25', '-c:a', 'libopus', '-b:a', '64k', '-f', 'webm', os.path.join(tifoto_dir, dest)])
subprocess.run(['touch', '-r', os.path.join(directory, video), os.path.join(tifoto_dir, dest)])
progress.end(video_increment)

progress.close()
show_message(_('Resized photos were written to "{}" and will be deleted at reboot.').format(tifoto_dir))
show_message(_('Resized photos and videos were written to "{}" and will be deleted at reboot.').format(tifoto_dir))

if __name__ == '__main__':

Expand All @@ -112,10 +137,10 @@ if __name__ == '__main__':
gettext.install('tifoto', '/usr/share/locale')

if len(sys.argv) < 2 or sys.argv[1] in ['-h', '--help']:
print(_('Usage: {} <all photos to resize>').format(sys.argv[0].split(os.sep)[-1]), file=sys.stderr)
print(_('Usage: {} <all photos and videos to resize>').format(sys.argv[0].split(os.sep)[-1]), file=sys.stderr)
sys.exit(1)

if sys.argv[1] == '--version':
print('tifoto v{}'.format(__version__))
else:
resize_photos(sys.argv[1:])
resize_files(sys.argv[1:])
12 changes: 6 additions & 6 deletions tifoto.pot
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
# tifoto v1.0.2
# (C) 2017 Jérémie Roquet <jroquet@arkanosis.net>
# tifoto v1.1.0
# (C) 2017-2018 Jérémie Roquet <jroquet@arkanosis.net>
# Released under the ISC license
# https://github.com/Arkanosis/tifoto/
##
msgid ""
msgstr ""
"Project-Id-Version: 1.0.2\n"
"Project-Id-Version: 1.1.0\n"
"POT-Creation-Date: 2017-06-08 02:46+0200\n"
"PO-Revision-Date: 2017-06-08 02:46+0200\n"
"PO-Revision-Date: 2018-05-19 02:46+0200\n"
"Last-Translator: Jérémie Roquet <jroquet@arkanosis.net>\n"
"Language-Team: Jérémie Roquet <jroquet@arkanosis.net>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

msgid "Usage: {} <all photos to resize>"
msgid "Usage: {} <all photos and videos to resize>"
msgstr ""

msgid "Resizing…"
Expand All @@ -23,5 +23,5 @@ msgstr ""
msgid "Resizing \"{}\"…"
msgstr ""

msgid "Resized photos were written to \"{}\" and will be deleted at reboot."
msgid "Resized photos and videos were written to \"{}\" and will be deleted at reboot."
msgstr ""

0 comments on commit d26521b

Please sign in to comment.