From 2b1a0948b2c8830542a8cfe0ab2c7c52cbb72f85 Mon Sep 17 00:00:00 2001 From: YoandyShyno Date: Wed, 5 Apr 2017 12:44:32 +0200 Subject: [PATCH 1/7] Functional script as initial commit An script to generate a video automatically from images in a directory. The files are generated in TMP dir. --- scripts/photofilmstrip-auto | 176 ++++++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 scripts/photofilmstrip-auto diff --git a/scripts/photofilmstrip-auto b/scripts/photofilmstrip-auto new file mode 100644 index 0000000..3f83b8d --- /dev/null +++ b/scripts/photofilmstrip-auto @@ -0,0 +1,176 @@ +#!/usr/bin/python +# -*- coding: utf-8 +""" + Contributed by Yoandy S +""" +import sqlite3 +import sys +import os +import time +import glob +import random +from photofilmstrip.CLI import main as photofilmstrip_cli + + +_duration = 0 +_secsperimg = 10 +_aspect = (16, 9) + + +def gen_project(pname): + conn = sqlite3.connect(pname) + c = conn.cursor() + # fields reference + # picture_id|filename|width|height|start_left|start_top|start_width|start_height|target_left|target_top|target_width|target_height|rotation|duration|movement|comment|effect|transition|transition_duration|data + # 1|/tmp/12/SAM_2636.jpg|1440|1080|0|0|1440|810|360|0|1080|607.5|0|7.0|1||0|1|1.0| + + c.execute('''CREATE TABLE `picture` ( + picture_id INTEGER PRIMARY KEY AUTOINCREMENT, + filename TEXT, + width INTEGER, + height INTEGER, + start_left INTEGER, + start_top INTEGER, + start_width INTEGER, + start_height INTEGER, + target_left INTEGER, + target_top INTEGER, + target_width INTEGER, + target_height INTEGER, + rotation INTEGER, + duration DOUBLE, + movement INTEGER, + comment TEXT, + effect INTEGER, + transition INTEGER, + transition_duration DOUBLE, + data BLOB + ) + ''') + c.execute('''CREATE TABLE `property` ( + property_id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT, + value TEXT + )''') + ''' + Should not be necessary for batch processing but for opening in visual + interface software + ''' + c.execute('''CREATE TABLE `thumbnail` ( + thumbnail_id INTEGER PRIMARY KEY AUTOINCREMENT, + picture_id INTEGER, + width INTEGER, + height INTEGER, + data BLOB, + FOREIGN KEY(picture_id) REFERENCES picture(picture_id) ON DELETE CASCADE + ) + ''') + conn.close() + + +def get_images(path, WD): + images = glob.glob(path + '*.jpg') + glob.glob(path + '*.JPG') + for p in images: + os.system('convert ' + p + ' -resize x1440 ' + WD + os.path.basename(p)) + + +def get_pos_coords(x, y): + # Asumming Full HD resolution 1920x1080 . 1440 height 4x3 original + global _aspect + (w, h) = (y, y * _aspect[1] / _aspect[0]) + k = random.randint(0, 3) + ca = [(0, 0, w, h), (x - w, 0, w, h), + (0, y - h, w, h), (x - w, y - h, w, h)] + return ca[k] + + +def process_images(path, pn): + # Process images and add soundtrack if a audio file is found. + global _secsperimg + global _aspect + (W, H) = (1920, 1440) + H1 = W * _aspect[1] / _aspect[0] + images = glob.glob(path + '*.jpg') + glob.glob(path + '*.JPG') + global _duration + _duration = len(images) * _secsperimg + conn = sqlite3.connect(path + pn) + c = conn.cursor() + # 1920x1080 + for p in images: + transition = random.randint(1, 2) + if random.randint(0, 1): + (start_left, start_top, start_width, start_height) = \ + (0, (W - H1) / 2, W, H1) + (target_left, target_top, target_width, target_height) = \ + get_pos_coords(W, H) + else: + (start_left, start_top, start_width, start_height) = \ + get_pos_coords(W, H) + (target_left, target_top, target_width, target_height) = \ + (0, (W - H1) / 2, W, H1) + c.execute('''INSERT into picture + (filename, width, height, start_left, start_top, start_width, + start_height, target_left, target_top, target_width, target_height, + rotation, duration, movement, comment, effect, transition, + transition_duration) + VALUES + ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''', + (p, W, H, start_left, start_top, start_width, start_height, + target_left, target_top, target_width, target_height, 0, + 7.0, 1, '', 0, transition, 1.0)) + # find mp3 + mp3s = glob.glob(path + '*.mp3') + if len(mp3s) > 0: + c.execute('insert into property values (?,?,?)', + (4, 'audiofile', mp3s[0])) + conn.commit() + conn.close() + + +def set_properties(path, pn): + global _aspect + conn = sqlite3.connect(path + pn) + c = conn.cursor() + c.execute('insert into property values (?, ?, ?)', (1, 'rev', '4')) + c.execute('insert into property values (?, ?, ?)', + (2, 'aspect', ':'.join(map(str, _aspect)))) + c.execute('insert into property values (?, ?, ?)', + (3, 'duration', _duration)) + conn.commit() + conn.close() + + +if __name__ == '__main__': + WD = '/tmp/' + WD += str(int(time.time())) + '/' + os.mkdir(WD) + # Collect data + if len(sys.argv) < 2: + print "USAGE: %s " % (sys.argv[0]) + exit() + else: + path = os.path.normpath(sys.argv[1]) + '/' + project_name = 'photofilmstrip-prj-' + str(os.stat(path).st_ino) + '.pfs' + project_path = WD + project_name + if os.path.exists(project_path): + print "WARNING: Project file already exist, will be overwritten" + get_images(path, WD) + + # im=Image.open(filepath) + # im.size # (width,height) tuple + # create database. + gen_project(project_path) + process_images(WD, project_name) + set_properties(WD, project_name) + sys.argv = [sys.argv[0]] +\ + '-p {} -o {} -t 2 -f 5'.format(project_path, WD).split() + photofilmstrip_cli() + # os.remove("test1.pfs") + +''' TODO: + X Add sound track + * Generate project for low quality TV, DVD, etc, ... + * Refactor code for error handling. + * Manage text subtitles automatically. + * Handle vertical pictures +''' From 437744d39946a1ea4e7af1635a9ede4a8401fd0d Mon Sep 17 00:00:00 2001 From: YoandyShyno Date: Sun, 9 Apr 2017 19:12:10 +0200 Subject: [PATCH 2/7] Refactoring code to reuse core classes - Unnecessary code is removed. - Project is generated using core classes. --- scripts/photofilmstrip-auto | 156 +++++--------------------------- windows/photofilmstrip-auto.bat | 2 + 2 files changed, 23 insertions(+), 135 deletions(-) create mode 100644 windows/photofilmstrip-auto.bat diff --git a/scripts/photofilmstrip-auto b/scripts/photofilmstrip-auto index 3f83b8d..c0dd647 100644 --- a/scripts/photofilmstrip-auto +++ b/scripts/photofilmstrip-auto @@ -3,13 +3,15 @@ """ Contributed by Yoandy S """ -import sqlite3 import sys import os import time import glob -import random +import subprocess from photofilmstrip.CLI import main as photofilmstrip_cli +from photofilmstrip.core.Project import Project +from photofilmstrip.core.ProjectFile import ProjectFile +from photofilmstrip.core.Picture import Picture _duration = 0 @@ -17,127 +19,25 @@ _secsperimg = 10 _aspect = (16, 9) -def gen_project(pname): - conn = sqlite3.connect(pname) - c = conn.cursor() - # fields reference - # picture_id|filename|width|height|start_left|start_top|start_width|start_height|target_left|target_top|target_width|target_height|rotation|duration|movement|comment|effect|transition|transition_duration|data - # 1|/tmp/12/SAM_2636.jpg|1440|1080|0|0|1440|810|360|0|1080|607.5|0|7.0|1||0|1|1.0| - - c.execute('''CREATE TABLE `picture` ( - picture_id INTEGER PRIMARY KEY AUTOINCREMENT, - filename TEXT, - width INTEGER, - height INTEGER, - start_left INTEGER, - start_top INTEGER, - start_width INTEGER, - start_height INTEGER, - target_left INTEGER, - target_top INTEGER, - target_width INTEGER, - target_height INTEGER, - rotation INTEGER, - duration DOUBLE, - movement INTEGER, - comment TEXT, - effect INTEGER, - transition INTEGER, - transition_duration DOUBLE, - data BLOB - ) - ''') - c.execute('''CREATE TABLE `property` ( - property_id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT, - value TEXT - )''') - ''' - Should not be necessary for batch processing but for opening in visual - interface software - ''' - c.execute('''CREATE TABLE `thumbnail` ( - thumbnail_id INTEGER PRIMARY KEY AUTOINCREMENT, - picture_id INTEGER, - width INTEGER, - height INTEGER, - data BLOB, - FOREIGN KEY(picture_id) REFERENCES picture(picture_id) ON DELETE CASCADE - ) - ''') - conn.close() +def make_project(path, project_name): + project = Project() + images = [Picture(picpath) for picpath in glob.glob(path + '*.jpg') + + glob.glob(path + '*.JPG')] + global _duration + global _secsperimg + _duration = len(images) * _secsperimg + project.SetDuration(_duration) + project.SetPictures(images) + project_file = ProjectFile(project, os.path.join(path, project_path)) + print(os.path.join(path, project_path)) + project_file.Save() def get_images(path, WD): images = glob.glob(path + '*.jpg') + glob.glob(path + '*.JPG') for p in images: - os.system('convert ' + p + ' -resize x1440 ' + WD + os.path.basename(p)) - - -def get_pos_coords(x, y): - # Asumming Full HD resolution 1920x1080 . 1440 height 4x3 original - global _aspect - (w, h) = (y, y * _aspect[1] / _aspect[0]) - k = random.randint(0, 3) - ca = [(0, 0, w, h), (x - w, 0, w, h), - (0, y - h, w, h), (x - w, y - h, w, h)] - return ca[k] - - -def process_images(path, pn): - # Process images and add soundtrack if a audio file is found. - global _secsperimg - global _aspect - (W, H) = (1920, 1440) - H1 = W * _aspect[1] / _aspect[0] - images = glob.glob(path + '*.jpg') + glob.glob(path + '*.JPG') - global _duration - _duration = len(images) * _secsperimg - conn = sqlite3.connect(path + pn) - c = conn.cursor() - # 1920x1080 - for p in images: - transition = random.randint(1, 2) - if random.randint(0, 1): - (start_left, start_top, start_width, start_height) = \ - (0, (W - H1) / 2, W, H1) - (target_left, target_top, target_width, target_height) = \ - get_pos_coords(W, H) - else: - (start_left, start_top, start_width, start_height) = \ - get_pos_coords(W, H) - (target_left, target_top, target_width, target_height) = \ - (0, (W - H1) / 2, W, H1) - c.execute('''INSERT into picture - (filename, width, height, start_left, start_top, start_width, - start_height, target_left, target_top, target_width, target_height, - rotation, duration, movement, comment, effect, transition, - transition_duration) - VALUES - ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''', - (p, W, H, start_left, start_top, start_width, start_height, - target_left, target_top, target_width, target_height, 0, - 7.0, 1, '', 0, transition, 1.0)) - # find mp3 - mp3s = glob.glob(path + '*.mp3') - if len(mp3s) > 0: - c.execute('insert into property values (?,?,?)', - (4, 'audiofile', mp3s[0])) - conn.commit() - conn.close() - - -def set_properties(path, pn): - global _aspect - conn = sqlite3.connect(path + pn) - c = conn.cursor() - c.execute('insert into property values (?, ?, ?)', (1, 'rev', '4')) - c.execute('insert into property values (?, ?, ?)', - (2, 'aspect', ':'.join(map(str, _aspect)))) - c.execute('insert into property values (?, ?, ?)', - (3, 'duration', _duration)) - conn.commit() - conn.close() + subprocess.call(["convert", p, "-resize", "x1440", + os.path.join(WD, os.path.basename(p))]) if __name__ == '__main__': @@ -146,31 +46,17 @@ if __name__ == '__main__': os.mkdir(WD) # Collect data if len(sys.argv) < 2: - print "USAGE: %s " % (sys.argv[0]) + print ("USAGE: %s " % (sys.argv[0])) exit() else: path = os.path.normpath(sys.argv[1]) + '/' project_name = 'photofilmstrip-prj-' + str(os.stat(path).st_ino) + '.pfs' project_path = WD + project_name if os.path.exists(project_path): - print "WARNING: Project file already exist, will be overwritten" + print ("WARNING: Project file already exist, will be overwritten") get_images(path, WD) - # im=Image.open(filepath) - # im.size # (width,height) tuple - # create database. - gen_project(project_path) - process_images(WD, project_name) - set_properties(WD, project_name) + make_project(WD, project_name) sys.argv = [sys.argv[0]] +\ '-p {} -o {} -t 2 -f 5'.format(project_path, WD).split() photofilmstrip_cli() - # os.remove("test1.pfs") - -''' TODO: - X Add sound track - * Generate project for low quality TV, DVD, etc, ... - * Refactor code for error handling. - * Manage text subtitles automatically. - * Handle vertical pictures -''' diff --git a/windows/photofilmstrip-auto.bat b/windows/photofilmstrip-auto.bat new file mode 100644 index 0000000..afc3732 --- /dev/null +++ b/windows/photofilmstrip-auto.bat @@ -0,0 +1,2 @@ +@"%~dp0..\python" "%~dp0photofilmstrip-auto" %* + From a53915a0a2c74ceaea6b3e8112175868daed61fd Mon Sep 17 00:00:00 2001 From: YoandyShyno Date: Mon, 17 Apr 2017 01:13:46 +0200 Subject: [PATCH 3/7] Cleaning Code. Introducing arguments handling (argparse) --- scripts/photofilmstrip-auto | 44 +++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 24 deletions(-) mode change 100644 => 100755 scripts/photofilmstrip-auto diff --git a/scripts/photofilmstrip-auto b/scripts/photofilmstrip-auto old mode 100644 new mode 100755 index c0dd647..bd2bb99 --- a/scripts/photofilmstrip-auto +++ b/scripts/photofilmstrip-auto @@ -3,11 +3,10 @@ """ Contributed by Yoandy S """ +import argparse import sys import os -import time import glob -import subprocess from photofilmstrip.CLI import main as photofilmstrip_cli from photofilmstrip.core.Project import Project from photofilmstrip.core.ProjectFile import ProjectFile @@ -19,7 +18,7 @@ _secsperimg = 10 _aspect = (16, 9) -def make_project(path, project_name): +def make_project(path, project_path): project = Project() images = [Picture(picpath) for picpath in glob.glob(path + '*.jpg') + glob.glob(path + '*.JPG')] @@ -28,35 +27,32 @@ def make_project(path, project_name): _duration = len(images) * _secsperimg project.SetDuration(_duration) project.SetPictures(images) - project_file = ProjectFile(project, os.path.join(path, project_path)) - print(os.path.join(path, project_path)) + project_file = ProjectFile(project, project_path) project_file.Save() -def get_images(path, WD): - images = glob.glob(path + '*.jpg') + glob.glob(path + '*.JPG') - for p in images: - subprocess.call(["convert", p, "-resize", "x1440", - os.path.join(WD, os.path.basename(p))]) - - if __name__ == '__main__': - WD = '/tmp/' - WD += str(int(time.time())) + '/' - os.mkdir(WD) - # Collect data - if len(sys.argv) < 2: - print ("USAGE: %s " % (sys.argv[0])) - exit() - else: - path = os.path.normpath(sys.argv[1]) + '/' + parser = argparse.ArgumentParser(description='Generate video from images in\ + a directory.') + parser.add_argument("directory_path", help="Path of directory with source\ + images.") + parser.add_argument("-d", "--destination_path", + help="Path where output files will be created.") + args = parser.parse_args() + if not args.destination_path: + WD = os.path.join(args.directory_path, "output") + try: + if not os.path.exists(WD): + os.mkdir(WD) + except PermissionError: + print("Permission denied: error creating directory.") + path = os.path.normpath(args.directory_path) + '/' project_name = 'photofilmstrip-prj-' + str(os.stat(path).st_ino) + '.pfs' - project_path = WD + project_name + project_path = os.path.join(WD, project_name) if os.path.exists(project_path): print ("WARNING: Project file already exist, will be overwritten") - get_images(path, WD) - make_project(WD, project_name) + make_project(path, project_path) sys.argv = [sys.argv[0]] +\ '-p {} -o {} -t 2 -f 5'.format(project_path, WD).split() photofilmstrip_cli() From 3be7808c8f7060dcf307b5c7384fc27d4dbefcd9 Mon Sep 17 00:00:00 2001 From: YoandyShyno Date: Mon, 17 Apr 2017 13:21:06 +0200 Subject: [PATCH 4/7] Using ActionAutoPath class for project generation. --- scripts/photofilmstrip-auto | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/scripts/photofilmstrip-auto b/scripts/photofilmstrip-auto index bd2bb99..f2d048d 100755 --- a/scripts/photofilmstrip-auto +++ b/scripts/photofilmstrip-auto @@ -11,7 +11,7 @@ from photofilmstrip.CLI import main as photofilmstrip_cli from photofilmstrip.core.Project import Project from photofilmstrip.core.ProjectFile import ProjectFile from photofilmstrip.core.Picture import Picture - +from photofilmstrip.action.ActionAutoPath import ActionAutoPath _duration = 0 _secsperimg = 10 @@ -20,8 +20,12 @@ _aspect = (16, 9) def make_project(path, project_path): project = Project() - images = [Picture(picpath) for picpath in glob.glob(path + '*.jpg') + - glob.glob(path + '*.JPG')] + images = [] + for picpath in glob.glob(path + '*.jpg') + glob.glob(path + '*.JPG'): + picture = Picture(picpath) + actAp = ActionAutoPath(picture, project.GetAspect()) + actAp.Execute() + images.append(picture) global _duration global _secsperimg _duration = len(images) * _secsperimg From 1cf5a1e299aee5777314a15e6101d242f2559b2b Mon Sep 17 00:00:00 2001 From: YoandyShyno Date: Mon, 17 Apr 2017 13:41:27 +0200 Subject: [PATCH 5/7] Adding option *duration* parameter. Duration of Video. --- scripts/photofilmstrip-auto | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/scripts/photofilmstrip-auto b/scripts/photofilmstrip-auto index f2d048d..8e422c9 100755 --- a/scripts/photofilmstrip-auto +++ b/scripts/photofilmstrip-auto @@ -13,12 +13,9 @@ from photofilmstrip.core.ProjectFile import ProjectFile from photofilmstrip.core.Picture import Picture from photofilmstrip.action.ActionAutoPath import ActionAutoPath -_duration = 0 -_secsperimg = 10 -_aspect = (16, 9) - -def make_project(path, project_path): +def make_project(path, project_path, duration): + SEC_X_IMG = 10 project = Project() images = [] for picpath in glob.glob(path + '*.jpg') + glob.glob(path + '*.JPG'): @@ -26,10 +23,10 @@ def make_project(path, project_path): actAp = ActionAutoPath(picture, project.GetAspect()) actAp.Execute() images.append(picture) - global _duration - global _secsperimg - _duration = len(images) * _secsperimg - project.SetDuration(_duration) + if not duration: + duration = len(images) * SEC_X_IMG + print(duration) + project.SetDuration(duration) project.SetPictures(images) project_file = ProjectFile(project, project_path) project_file.Save() @@ -42,6 +39,8 @@ if __name__ == '__main__': images.") parser.add_argument("-d", "--destination_path", help="Path where output files will be created.") + parser.add_argument("-u", "--duration", default=0, type=int, + help="Set the duration of the output Video.") args = parser.parse_args() if not args.destination_path: WD = os.path.join(args.directory_path, "output") @@ -56,7 +55,7 @@ if __name__ == '__main__': if os.path.exists(project_path): print ("WARNING: Project file already exist, will be overwritten") - make_project(path, project_path) + make_project(path, project_path, args.duration) sys.argv = [sys.argv[0]] +\ '-p {} -o {} -t 2 -f 5'.format(project_path, WD).split() photofilmstrip_cli() From 6f03cc865e32ad1b9d016962aeeee23b155c172c Mon Sep 17 00:00:00 2001 From: YoandyShyno Date: Mon, 17 Apr 2017 13:54:42 +0200 Subject: [PATCH 6/7] Adding time-per-image optional parameter. Minor refactoring. --- scripts/photofilmstrip-auto | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/scripts/photofilmstrip-auto b/scripts/photofilmstrip-auto index 8e422c9..48c48bf 100755 --- a/scripts/photofilmstrip-auto +++ b/scripts/photofilmstrip-auto @@ -14,8 +14,7 @@ from photofilmstrip.core.Picture import Picture from photofilmstrip.action.ActionAutoPath import ActionAutoPath -def make_project(path, project_path, duration): - SEC_X_IMG = 10 +def make_project(path, project_path, duration, time_per_image): project = Project() images = [] for picpath in glob.glob(path + '*.jpg') + glob.glob(path + '*.JPG'): @@ -24,8 +23,7 @@ def make_project(path, project_path, duration): actAp.Execute() images.append(picture) if not duration: - duration = len(images) * SEC_X_IMG - print(duration) + duration = len(images) * time_per_image project.SetDuration(duration) project.SetPictures(images) project_file = ProjectFile(project, project_path) @@ -37,11 +35,16 @@ if __name__ == '__main__': a directory.') parser.add_argument("directory_path", help="Path of directory with source\ images.") - parser.add_argument("-d", "--destination_path", + parser.add_argument("-d", "--destination-path", help="Path where output files will be created.") parser.add_argument("-u", "--duration", default=0, type=int, help="Set the duration of the output Video.") + parser.add_argument("-x", "--time-per-image", default=10, type=int, + help="This is use to calculate the total time based on\ + the number of images.") args = parser.parse_args() + if args.time_per_image and args.duration: + print("WARNING: The time-per-image Value is ignored.") if not args.destination_path: WD = os.path.join(args.directory_path, "output") try: @@ -55,7 +58,7 @@ if __name__ == '__main__': if os.path.exists(project_path): print ("WARNING: Project file already exist, will be overwritten") - make_project(path, project_path, args.duration) + make_project(path, project_path, args.duration, args.time_per_image) sys.argv = [sys.argv[0]] +\ '-p {} -o {} -t 2 -f 5'.format(project_path, WD).split() photofilmstrip_cli() From a61553f344258a2436cf201c0d09ae8bf60fc0ae Mon Sep 17 00:00:00 2001 From: YoandyShyno Date: Mon, 17 Apr 2017 14:18:13 +0200 Subject: [PATCH 7/7] add photofilmstrip-auto to build list. --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index 831550f..3cb7765 100644 --- a/setup.py +++ b/setup.py @@ -362,6 +362,7 @@ def Unzip(zipFile, targetDir, stripFolders=0): if os.name == "nt": platform_scripts.append("windows/photofilmstrip.bat") platform_scripts.append("windows/photofilmstrip-cli.bat") + platform_scripts.append("windows/photofilmstrip-auto.bat") platform_data.append((os.path.join("share", "doc", "photofilmstrip"), ["windows/photofilmstrip.chm"])) else: platform_data.append(("share/applications", ["data/photofilmstrip.desktop"])) @@ -462,6 +463,7 @@ def Unzip(zipFile, targetDir, stripFolders=0): scripts=[ "scripts/photofilmstrip", "scripts/photofilmstrip-cli", + "scripts/photofilmstrip-auto" ] + platform_scripts, name = Constants.APP_NAME.lower(),