关于Unix:如何在不重新压缩JPEG的情况下删除EXIF数据?

How to remove EXIF data without recompressing the JPEG?

我想从JPEG文件中删除EXIF信息(包括缩略图,元数据,相机信息...所有内容!),但是我不想重新压缩它,因为重新压缩JPEG会降低质量,并且通常会增加 文件大小。

我正在寻找Unix / Linux解决方案,如果使用命令行则更好。 如果可能,请使用ImageMagick(转换工具)。 如果这不可能,那么可以使用小型Python,Perl,PHP(或Linux上的其他公共语言)脚本。

有一个类似的问题,但与.NET有关。


exiftool为我完成了工作,它是用perl编写的,因此在任何操作系统上都可以为您工作

https://exiftool.org/

用法:

1
exiftool -all= image.jpg


使用imagemagick:

1
convert <input file> -strip <output file>


ImageMagick具有-strip参数,但是在保存之前会重新压缩图像。因此,此参数对我的需求毫无用处。

来自ImageMagick论坛的本主题说明,ImageMagick不支持JPEG无损操作(每当更改时,请发表带有链接的评论!),并建议使用jpegtran(来自libjpeg):

1
2
jpegtran -copy none -progressive image.jpg > newimage.jpg
jpegtran -copy none -progressive -outfile newimage.jpg image.jpg

(如果您不确定我是否回答我自己的问题,请阅读此内容以及该内容)


您可能还想研究一下Exiv2,它确实非常快(C ++,无需重新压缩),它是命令行,并且它还提供了可链接的EXIF操作库。我不知道有多少Linux发行版可以使用它,但是在CentOS中它目前在基本存储库中可用。

用法:

1
exiv2 rm image.jpg


我建议jhead

1
2
man jhead
jhead -purejpg image.jpg

debian / ubuntu上只有123Kb,不会重新压缩。请注意,尽管它会改变图像,但是如果需要,请复制原始图像。


我最近在C中进行了这个项目。下面的代码执行以下操作:

1)获取图像的当前方向。

2)通过消隐删除APP1(Exif数据)和APP2(Flashpix数据)中包含的所有数据。

3)重新创建APP1方向标记并将其设置为原始值。

4)查找第一个EOI标记(图像结尾),并在必要时将其截断。

首先要注意的是:

1)该程序用于我的尼康相机。尼康的JPEG格式在其创建的每个文件的末尾添加了一些东西。他们通过创建第二个EOI标记将该数据编码到图像文件的末尾。通常,图像程序最多读取找到的第一个EOI标记。尼康之后有我的程序会截断的信息。

2)因为这是尼康格式,所以它假定big endian字节顺序。如果图像文件使用little endian,则需要进行一些调整。

3)当尝试使用ImageMagick剥离exif数据时,我注意到最终得到的文件比开始时要大。这使我相信ImageMagick正在编码要剥离的数据,并将其存储在文件中的其他位置。称我为老式,但是当我从文件中删除某些内容时,我希望文件大小要小一些,即使大小不一样。其他任何结果都建议进行数据挖掘。

这是代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
#include <stdio.h>
#include <stdlib.h>
#include <libgen.h>
#include <string.h>
#include <errno.h>

// Declare constants.
#define COMMAND_SIZE     500
#define RETURN_SUCCESS     1
#define RETURN_FAILURE     0
#define WORD_SIZE         15

int check_file_jpg (void);
int check_file_path (char *file);
int get_marker (void);
char * ltoa (long num);
void process_image (char *file);

// Declare global variables.
FILE *fp;
int orientation;
char *program_name;

int main (int argc, char *argv[])
{
// Set program name for error reporting.
    program_name = basename(argv[0]);

// Check for at least one argument.
    if(argc < 2)
    {
        fprintf(stderr,"usage: %s IMAGE_FILE...\
", program_name);
        exit(EXIT_FAILURE);
    }

// Process all arguments.
    for(int x = 1; x < argc; x++)
        process_image(argv[x]);

    exit(EXIT_SUCCESS);
}

void process_image (char *file)
{
    char command[COMMAND_SIZE + 1];

// Check that file exists.
    if(check_file_path(file) == RETURN_FAILURE)
        return;

// Check that file is an actual JPEG file.
    if(check_file_jpg() == RETURN_FAILURE)
    {
        fclose(fp);
        return;
    }

// Jump to orientation marker and store value.
    fseek(fp, 55, SEEK_SET);
    orientation = fgetc(fp);

// Recreate the APP1 marker with just the orientation tag listed.
    fseek(fp, 21, SEEK_SET);
    fputc(1, fp);

    fputc(1, fp);
    fputc(18, fp);
    fputc(0, fp);
    fputc(3, fp);
    fputc(0, fp);
    fputc(0, fp);
    fputc(0, fp);
    fputc(1, fp);
    fputc(0, fp);
    fputc(orientation, fp);

// Blank the rest of the APP1 marker with '\\0'.
    for(int x = 0; x < 65506; x++)
        fputc(0, fp);

// Blank the second APP1 marker with '\\0'.
    fseek(fp, 4, SEEK_CUR);

    for(int x = 0; x < 2044; x++)
        fputc(0, fp);

// Blank the APP2 marker with '\\0'.
    fseek(fp, 4, SEEK_CUR);

    for(int x = 0; x < 4092; x++)
        fputc(0, fp);

// Jump the the SOS marker.
    fseek(fp, 72255, SEEK_SET);

    while(1)
    {
// Truncate the file once the first EOI marker is found.
        if(fgetc(fp) == 255 && fgetc(fp) == 217)
        {
            strcpy(command,"truncate -s");
            strcat(command, ltoa(ftell(fp)));
            strcat(command,"");
            strcat(command, file);
            fclose(fp);
            system(command);
            break;
        }
    }
}

int get_marker (void)
{
    int c;

// Check to make sure marker starts with 0xFF.
    if((c = fgetc(fp)) != 0xFF)
    {
        fprintf(stderr,"%s: get_marker: invalid marker start (should be FF, is %2X)\
", program_name, c);
        return(RETURN_FAILURE);
    }

// Return the next character.
    return(fgetc(fp));
}

int check_file_jpg (void)
{
// Check if marker is 0xD8.
    if(get_marker() != 0xD8)
    {
        fprintf(stderr,"%s: check_file_jpg: not a valid jpeg image\
", program_name);
        return(RETURN_FAILURE);
    }

    return(RETURN_SUCCESS);
}

int check_file_path (char *file)
{
// Open file.
    if((fp = fopen(file,"rb+")) == NULL)
    {
        fprintf(stderr,"%s: check_file_path: fopen failed (%s) (%s)\
", program_name, strerror(errno), file);
        return(RETURN_FAILURE);
    }

    return(RETURN_SUCCESS);
}

char * ltoa (long num)
{
// Declare variables.
        int ret;
        int x = 1;
        int y = 0;
        static char temp[WORD_SIZE + 1];
        static char word[WORD_SIZE + 1];

// Stop buffer overflow.
        temp[0] = '\\0';

// Keep processing until value is zero.
        while(num > 0)
        {
                ret = num % 10;
                temp[x++] = 48 + ret;
                num /= 10;
        }

// Reverse the word.
        while(y < x)
        {
                word[y] = temp[x - y - 1];
                y++;
        }

        return word;
}

希望这对某人有帮助!


对于无损EXIF条带,可以使用cygwin随附的libexif。删除EXIF和缩略图以匿名化图像:

1
$ exif --remove --tag=0 --remove-thumbnail exif.jpg -o anonymized.jpg

拖放.bat文件以与cygwin一起使用:

1
2
@ECHO OFF
exif --remove --tag=0 --remove-thumbnail %~1

为了方便起见:如果您使用的是Windows,则可以将REG文件应用于注册表,以在上下文菜单中安装条目,因此可以通过右键单击文件并选择命令来轻松删除元数据。

例如(记住编辑路径以指向可执行文件在计算机上的安装位置):

对于JPEG,JPG,JPE,JFIF文件:命令"删除元数据"
(使用ExifTool,将原始文件保留为备份)
exiftool -all= image.jpg

JPG-RemoveExif.reg

1
2
3
4
5
6
7
8
9
10
Windows Registry Editor Version 5.00
[HKEY_CURRENT_USER\\Software\\Classes\\jpegfile\\shell\
emoveMetadata]
@="Remove metadata"
[HKEY_CURRENT_USER\\Software\\Classes\\jpegfile\\shell\
emoveMetadata\\command]
@="\"C:\\\\Path to\\\\exiftool.exe\" -all= \"%1\""
[HKEY_CURRENT_USER\\Software\\Classes\\jpegfile\\shell\
emoveMetadata]
"Icon"="C:\\\\Path to\\\\exiftool.exe,0"

对于PNG文件:命令"转换为缩小的PNG"
(使用ImageMagick,更改覆盖原始文件的数据)
convert -background none -strip -set filename:n"%t" image.png"%[filename:n].png"

PNG-Minify.reg

1
2
3
4
5
6
7
Windows Registry Editor Version 5.00
[HKEY_CURRENT_USER\\Software\\Classes\\pngfile\\shell\\ConvertToMinifiedPNG]
@="Convert to minified PNG"
[HKEY_CURRENT_USER\\Software\\Classes\\pngfile\\shell\\ConvertToMinifiedPNG\\command]
@="\"C:\\\\Path to\\\\convert.exe\" -background none -strip -set filename:n \"%%t\" \"%1\" \"%%[filename:n].png\""
[HKEY_CURRENT_USER\\Software\\Classes\\pngfile\\shell\\ConvertToMinifiedPNG]
"Icon"="C:\\\\Path to\\\\convert.exe,0"

相关:在上下文菜单中将PNG转换为ICO。


如果您已经使用jpegoptim,则也可以使用它来删除exif。

1
jpegoptim -s *

我们用它从TIFF文件中删除纬度数据:

exiv2 mo -M"del Exif.GPSInfo.GPSLatitude" IMG.TIF
您可以在其中使用exiv2 -pa IMG.TIF列出所有元数据。


其他软件:

MetAbility QuickFix

" MetabilityQuickFix只需单击一下鼠标,即可从所有照片中剥离所有个人信息和GPS位置数据。它可以安全地从JPEG文件中清除Exif,Iptc和XMP数据块中的所有元数据项,并自动制作原始文件的备份副本"

JPEG和PNG剥离器

"用于从JPG / JPEG / JFIF和PNG文件中剥离/清除/删除不必要的元数据(垃圾)的工具。图像质量不受影响。包括命令行支持。只需在命令行上指定文件夹或文件(允许使用通配符)"