diff --git a/daemon.py b/daemon.py index 4f25f5e7e..dbde39557 100644 --- a/daemon.py +++ b/daemon.py @@ -758,7 +758,8 @@ def run_daemon(accounts_data_dir: str, unit_test: bool, instance_only_skills_search: bool, send_threads: [], - manual_follower_approval: bool) -> None: + manual_follower_approval: bool, + watermark_width_percent: int) -> None: if len(domain) == 0: domain = 'localhost' if '.' not in domain: @@ -802,6 +803,10 @@ def run_daemon(accounts_data_dir: str, httpd.starting_daemon = True + # width of watermark applied to attached images + # as a percentage of the attached image width + httpd.watermark_width_percent = watermark_width_percent + # for each account whether to hide announces httpd.hide_announces = {} hide_announces_filename = data_dir(base_dir) + '/hide_announces.json' diff --git a/daemon_post.py b/daemon_post.py index b53c456d5..971c62295 100644 --- a/daemon_post.py +++ b/daemon_post.py @@ -867,7 +867,8 @@ def daemon_http_post(self) -> None: self.server.block_federated, self.server.onion_domain, self.server.i2p_domain, - self.server.max_shares_on_profile) + self.server.max_shares_on_profile, + self.server.watermark_width_percent) if page_number: print(curr_post_type + ' post received') nickname = self.path.split('/users/')[1] diff --git a/daemon_post_receive.py b/daemon_post_receive.py index 6acbb2d2b..b0c7e817f 100644 --- a/daemon_post_receive.py +++ b/daemon_post_receive.py @@ -11,7 +11,6 @@ import os import time import copy import errno -import subprocess from socket import error as SocketError from shares import add_share from languages import get_understood_languages @@ -22,13 +21,13 @@ from content import add_html_tags from content import extract_text_fields_in_post from content import extract_media_in_form_post from content import save_media_in_form_post +from media import apply_watermark_to_image from media import replace_twitter from media import replace_you_tube from media import process_meta_data from media import convert_image_to_low_bandwidth from media import attach_media from city import get_spoofed_city -from utils import safe_system_string from utils import get_instance_url from utils import is_float from utils import save_json @@ -55,7 +54,6 @@ from inbox import populate_replies from inbox import update_edited_post from daemon_utils import post_to_outbox from webapp_column_right import html_citations -from webapp_utils import get_watermark_file from httpheaders import set_headers from httpcodes import write2 from cache import store_person_in_cache @@ -69,44 +67,6 @@ NEW_POST_FAILED = -1 NEW_POST_CANCELLED = 2 -def _apply_watermark_to_image(base_dir: str, nickname: str, domain: str, - post_image_filename: str) -> bool: - """Applies a watermark to the given image - """ - if not os.path.isfile(post_image_filename): - return False - if not os.path.isfile('/usr/bin/composite'): - return False - _, watermark_filename = get_watermark_file(base_dir, nickname, domain) - if not watermark_filename: - return False - if not os.path.isfile(watermark_filename): - return False - cmd = \ - '/usr/bin/composite -watermark 10% -gravity east ' + \ - safe_system_string(watermark_filename) + ' ' + \ - safe_system_string(post_image_filename) + ' ' + \ - safe_system_string(post_image_filename + '.watermarked') - subprocess.call(cmd, shell=True) - if not os.path.isfile(post_image_filename + '.watermarked'): - return False - - try: - os.remove(post_image_filename) - except OSError: - print('EX: _apply_watermark_to_image unable to remove ' + - post_image_filename) - return False - - try: - os.rename(post_image_filename + '.watermarked', post_image_filename) - except OSError: - print('EX: _apply_watermark_to_image unable to rename ' + - post_image_filename + '.watermarked') - return False - return True - - def _receive_new_post_process_newpost(self, fields: {}, base_dir: str, nickname: str, domain: str, domain_full: str, port: int, @@ -1635,7 +1595,8 @@ def _receive_new_post_process(self, post_type: str, path: str, headers: {}, block_federated: [], onion_domain: str, i2p_domain: str, - max_shares_on_profile: int) -> int: + max_shares_on_profile: int, + watermark_width_percent: int) -> int: # Note: this needs to happen synchronously # 0=this is not a new post # 1=new post success @@ -1727,8 +1688,8 @@ def _receive_new_post_process(self, post_type: str, path: str, headers: {}, if low_bandwidth: print('Converting to low bandwidth ' + filename) convert_image_to_low_bandwidth(filename) - _apply_watermark_to_image(base_dir, nickname, domain, - filename) + apply_watermark_to_image(base_dir, nickname, domain, + filename, watermark_width_percent) post_image_filename = filename.replace('.temp', '') print('Removing metadata from ' + post_image_filename) city = get_spoofed_city(city, base_dir, nickname, domain) @@ -2235,7 +2196,8 @@ def receive_new_post(self, post_type, path: str, block_federated: [], onion_domain: str, i2p_domain: str, - max_shares_on_profile: int) -> int: + max_shares_on_profile: int, + watermark_width_percent: int) -> int: """A new post has been created This creates a thread to send the new post """ @@ -2373,7 +2335,8 @@ def receive_new_post(self, post_type, path: str, dm_license_url, block_federated, onion_domain, i2p_domain, - max_shares_on_profile) + max_shares_on_profile, + watermark_width_percent) if debug: print('DEBUG: _receive_new_post_process returned ' + str(retval)) diff --git a/epicyon.py b/epicyon.py index ff4e16cfc..27fd34544 100644 --- a/epicyon.py +++ b/epicyon.py @@ -251,6 +251,11 @@ def _command_options() -> None: default=None, help='Number of days after which posts expire ' + 'for the given account') + parser.add_argument('--watermarkWidthPercent', + dest='watermark_width_percent', type=int, + default=20, + help='Width of the watermark applied to attached ' + + 'images as a percentage of the attached image width') parser.add_argument('--check-actor-timeout', dest='check_actor_timeout', type=int, default=2, help='Timeout in seconds used for checking is ' + @@ -4080,4 +4085,5 @@ if __name__ == "__main__": argb2.account_max_posts_per_day, argb2.allowdeletion, opt2['debug'], False, argb2.instance_only_skills_search, [], - not argb2.noapproval) + not argb2.noapproval, + argb2.watermark_width_percent) diff --git a/media.py b/media.py index 6b7c9aa0f..25f9d7a26 100644 --- a/media.py +++ b/media.py @@ -26,6 +26,7 @@ from utils import get_audio_extensions from utils import get_media_extensions from utils import has_object_dict from utils import acct_dir +from utils import get_watermark_file from shutil import copyfile from shutil import rmtree from shutil import move @@ -760,3 +761,57 @@ def get_image_dimensions(image_filename: str) -> (int, int): if not height_str.isdigit(): return None, None return int(width_str), int(height_str) + + +def apply_watermark_to_image(base_dir: str, nickname: str, domain: str, + post_image_filename: str, + watermark_width_percent: int) -> bool: + """Applies a watermark to the given image + """ + if not os.path.isfile(post_image_filename): + return False + if not os.path.isfile('/usr/bin/composite'): + return False + _, watermark_filename = get_watermark_file(base_dir, nickname, domain) + if not watermark_filename: + return False + if not os.path.isfile(watermark_filename): + return False + + # scale the watermark so that it is a fixed percentage of the image width + post_image_width, _ = \ + get_image_dimensions(post_image_filename) + watermark_image_width, watermark_image_height = \ + get_image_dimensions(post_image_filename) + scaled_watermark_image_width = \ + int(post_image_width * watermark_width_percent / 100) + scaled_watermark_image_height = \ + int(watermark_image_height * + scaled_watermark_image_width / watermark_image_width) + + cmd = \ + '/usr/bin/composite ' + \ + '-geometry ' + str(scaled_watermark_image_width) + 'x' + \ + str(scaled_watermark_image_height) + '+30+5 ' + \ + '-watermark 10% -gravity east ' + \ + safe_system_string(watermark_filename) + ' ' + \ + safe_system_string(post_image_filename) + ' ' + \ + safe_system_string(post_image_filename + '.watermarked') + subprocess.call(cmd, shell=True) + if not os.path.isfile(post_image_filename + '.watermarked'): + return False + + try: + os.remove(post_image_filename) + except OSError: + print('EX: _apply_watermark_to_image unable to remove ' + + post_image_filename) + return False + + try: + os.rename(post_image_filename + '.watermarked', post_image_filename) + except OSError: + print('EX: _apply_watermark_to_image unable to rename ' + + post_image_filename + '.watermarked') + return False + return True diff --git a/tests.py b/tests.py index 5b68864e5..7fbfe8d06 100644 --- a/tests.py +++ b/tests.py @@ -893,6 +893,7 @@ def create_server_alice(path: str, domain: str, port: int, public_replies_unlisted = False no_of_books = 10 accounts_data_dir = None + watermark_width_percent = 20 print('Server running: Alice') run_daemon(accounts_data_dir, no_of_books, public_replies_unlisted, @@ -924,7 +925,7 @@ def create_server_alice(path: str, domain: str, port: int, proxy_type, max_replies, domain_max_posts_per_day, account_max_posts_per_day, allow_deletion, True, True, False, send_threads, - False) + False, watermark_width_percent) def create_server_bob(path: str, domain: str, port: int, @@ -1079,6 +1080,7 @@ def create_server_bob(path: str, domain: str, port: int, public_replies_unlisted = False no_of_books = 10 accounts_data_dir = None + watermark_width_percent = 20 print('Server running: Bob') run_daemon(accounts_data_dir, no_of_books, public_replies_unlisted, @@ -1110,7 +1112,7 @@ def create_server_bob(path: str, domain: str, port: int, proxy_type, max_replies, domain_max_posts_per_day, account_max_posts_per_day, allow_deletion, True, True, False, send_threads, - False) + False, watermark_width_percent) def create_server_eve(path: str, domain: str, port: int, federation_list: [], @@ -1173,6 +1175,7 @@ def create_server_eve(path: str, domain: str, port: int, federation_list: [], domain_max_posts_per_day = 1000 account_max_posts_per_day = 1000 accounts_data_dir = None + watermark_width_percent = 20 print('Server running: Eve') run_daemon(accounts_data_dir, no_of_books, public_replies_unlisted, @@ -1224,7 +1227,8 @@ def create_server_eve(path: str, domain: str, port: int, federation_list: [], account_max_posts_per_day, allow_deletion, True, True, False, - send_threads, False) + send_threads, False, + watermark_width_percent) def create_server_group(path: str, domain: str, port: int, @@ -1289,6 +1293,7 @@ def create_server_group(path: str, domain: str, port: int, public_replies_unlisted = False no_of_books = 10 accounts_data_dir = None + watermark_width_percent = 20 print('Server running: Group') run_daemon(accounts_data_dir, no_of_books, public_replies_unlisted, @@ -1320,7 +1325,7 @@ def create_server_group(path: str, domain: str, port: int, proxy_type, max_replies, domain_max_posts_per_day, account_max_posts_per_day, allow_deletion, True, True, False, send_threads, - False) + False, watermark_width_percent) def test_post_message_between_servers(base_dir: str) -> None: diff --git a/utils.py b/utils.py index 7eb8258b5..3a1ef90b6 100644 --- a/utils.py +++ b/utils.py @@ -5586,3 +5586,43 @@ def remove_link_tracking(url: str) -> str: if '?utm_' not in url: return url return url.split('?utm_')[0] + + +def get_image_file(base_dir: str, name: str, directory: str, + theme: str) -> (str, str): + """returns the filenames for an image with the given name + """ + banner_extensions = get_image_extensions() + banner_file = '' + banner_filename = '' + im_name = name + for ext in banner_extensions: + banner_file_test = im_name + '.' + ext + banner_filename_test = directory + '/' + banner_file_test + if os.path.isfile(banner_filename_test): + banner_file = banner_file_test + banner_filename = banner_filename_test + return banner_file, banner_filename + # if not found then use the default image + curr_theme = 'default' + if theme: + curr_theme = theme + directory = base_dir + '/theme/' + curr_theme + for ext in banner_extensions: + banner_file_test = name + '.' + ext + banner_filename_test = directory + '/' + banner_file_test + if os.path.isfile(banner_filename_test): + banner_file = name + '_' + curr_theme + '.' + ext + banner_filename = banner_filename_test + break + return banner_file, banner_filename + + +def get_watermark_file(base_dir: str, + nickname: str, domain: str) -> (str, str): + """Gets the filename for watermarking when an image is attached to a post + """ + account_dir = acct_dir(base_dir, nickname, domain) + watermark_file, watermark_filename = \ + get_image_file(base_dir, 'watermark_image', account_dir, '') + return watermark_file, watermark_filename diff --git a/webapp_utils.py b/webapp_utils.py index 7ff11695d..677851600 100644 --- a/webapp_utils.py +++ b/webapp_utils.py @@ -12,6 +12,7 @@ from shutil import copyfile from collections import OrderedDict from session import get_json from session import get_json_valid +from utils import get_image_file from utils import data_dir from utils import string_contains from utils import get_post_attachments @@ -643,44 +644,13 @@ def post_contains_public(post_json_object: {}) -> bool: return contains_public -def _get_image_file(base_dir: str, name: str, directory: str, - theme: str) -> (str, str): - """ - returns the filenames for an image with the given name - """ - banner_extensions = get_image_extensions() - banner_file = '' - banner_filename = '' - im_name = name - for ext in banner_extensions: - banner_file_test = im_name + '.' + ext - banner_filename_test = directory + '/' + banner_file_test - if os.path.isfile(banner_filename_test): - banner_file = banner_file_test - banner_filename = banner_filename_test - return banner_file, banner_filename - # if not found then use the default image - curr_theme = 'default' - if theme: - curr_theme = theme - directory = base_dir + '/theme/' + curr_theme - for ext in banner_extensions: - banner_file_test = name + '.' + ext - banner_filename_test = directory + '/' + banner_file_test - if os.path.isfile(banner_filename_test): - banner_file = name + '_' + curr_theme + '.' + ext - banner_filename = banner_filename_test - break - return banner_file, banner_filename - - def get_banner_file(base_dir: str, nickname: str, domain: str, theme: str) -> (str, str): """Gets the image for the timeline banner """ account_dir = acct_dir(base_dir, nickname, domain) banner_file, banner_filename = \ - _get_image_file(base_dir, 'banner', account_dir, theme) + get_image_file(base_dir, 'banner', account_dir, theme) return banner_file, banner_filename @@ -691,7 +661,7 @@ def get_profile_background_file(base_dir: str, """ account_dir = acct_dir(base_dir, nickname, domain) banner_file, banner_filename = \ - _get_image_file(base_dir, 'image', account_dir, theme) + get_image_file(base_dir, 'image', account_dir, theme) return banner_file, banner_filename @@ -702,7 +672,7 @@ def get_search_banner_file(base_dir: str, """ account_dir = acct_dir(base_dir, nickname, domain) banner_file, banner_filename = \ - _get_image_file(base_dir, 'search_banner', account_dir, theme) + get_image_file(base_dir, 'search_banner', account_dir, theme) return banner_file, banner_filename @@ -712,7 +682,7 @@ def get_left_image_file(base_dir: str, """ account_dir = acct_dir(base_dir, nickname, domain) banner_file, banner_filename = \ - _get_image_file(base_dir, 'left_col_image', account_dir, theme) + get_image_file(base_dir, 'left_col_image', account_dir, theme) return banner_file, banner_filename @@ -722,20 +692,10 @@ def get_right_image_file(base_dir: str, """ account_dir = acct_dir(base_dir, nickname, domain) banner_file, banner_filename = \ - _get_image_file(base_dir, 'right_col_image', account_dir, theme) + get_image_file(base_dir, 'right_col_image', account_dir, theme) return banner_file, banner_filename -def get_watermark_file(base_dir: str, - nickname: str, domain: str) -> (str, str): - """Gets the filename for watermarking when an image is attached to a post - """ - account_dir = acct_dir(base_dir, nickname, domain) - watermark_file, watermark_filename = \ - _get_image_file(base_dir, 'watermark_image', account_dir, '') - return watermark_file, watermark_filename - - def html_header_with_external_style(css_filename: str, instance_title: str, metadata: str, lang='en') -> str: if metadata is None: