照片被Mac删除以及提取缩略图

2020-03-13  本文已影响0人  健身营养爱好者

记录一下3月8号遇到的照片问题:

我的所有照片都存放在Mac自带的图库里,而且只有一份,文件导入后会删掉。当天我一次导入了500张照片,看一下哪些时间轴是有错误的,当时导入的时候就觉得有点慢,看完之后对图片进行删除,并去最近删除里清空,然后它就一直没有反应(之前也遇到过这样的情况,所以并没有很在意),于是按下option进行强退,再次打开,此时桌面中间有个Toast的东西显示正在关闭图库,之前从未出现过这种状态,然后就打不开了,我看了下【照片图库.photoslibrary】仍然是之前的18G,就没有担心。重启电脑之后打开图库显示没有照片,于是我去看【照片图库.photoslibrary】变成了1.6G,当时我就知道这些数据是被Mac图库自身给删除了。

随后自行查找恢复方法,并没有开启icloud备份和时光机,本地也没有备份,网盘里有部分照片但在10天前已经删除了,很绝望。

第二天联系苹果客服,搜集了一些日志暂无结果。同时看百度网盘的超级会员,也只是开通后才保留30天,而不是可以找回30天内删除的文件,我试着上传了20天外删除的照片显示秒传,说明那些文件其实还是存在于百度网盘上的,于是联系网盘负责人,但是对方不予恢复。

然后我试着用软件进行数据恢复,之前我在PC上尝试的效果很好,但是Mac上却只能扫除本地存在的照片,不过我发现了HandShaker和百度网盘的缩略图文件,后来才知道SSD硬盘用软件恢复几乎无效。

我去看photoslibrary里面的内容,找到55张之前删除的图片,看看这剩下的1G内容是什么,在segment下面找到了这样的内容,看字面意思就是缩略图文件片段。


segment目录

到此为止我不太明确知道具体丢失了哪些照片,于是随便拿一个文件ThumbJPGSegment_20.data将它改名为zip,rar,7z都无法打开,后来想应该是把图片做成数据文件集合了,而不是图片目录,于是开始试着恢复缩略图。

我用记事本以UTF8打开图片文件,显示乱码,但是可以看懂部分内容,发现8Photoshop可以是作为分隔符的,而且开头是EXIF信息,结尾也似乎有固定的格式,于是尝试把头尾弄出来,保存成jpg,无法显示。


记事本打开

后来用sublime打开数据文件进行复制,这里可以选择使用UTF8打开或者二进制打开,在UTF8模式下发现含有0x00的内容都无法复制,于是使用二进制的格式进行复制,中间出现了一个错误,就是把复制的二进制内容当成了UTF8的内容保存成了文件,这样文件就是一堆数字,相当于是txt,必须先把目标文件存在本地,然后以二进制打开,再进行内容复制,这样得到的就是二进制数据。


sublime打开

先试着保存一张图片,修改成jpg后依然无法显示,开头结尾我认为应该是对的,我想可能是文件头出了问题,文件头不对解析会出错,如果是内容体或尾部不对只会显示出部分,下面是黑的或者花绿的。我把现存的JPG文件用二进制打开看了看,然后查找了一下JPG的二进制格式规则,以ffd8开头,以ffd9结尾,发现苹果的片段在ffd8之前都有一个其他四位数,于是去掉了这4位数,把ffd9后的数据也都删除,此时图片就显示出来了。

JPG二进制格式 这是显示出来的第一张图片

当时很开心,说明还是文件头的问题,于是我又手动试了几个,都可以显示,那么我就开始准备自己去解析出所有的缩略图,看看都丢了哪些文件。这就可以变成是一个算法题,读入一个二进制文件,当遇到ffd8时开始解析,当遇到包含ffd9时停止解析,并把解析内容写入文件保存。结尾符不一定完全是ffd9,可以是ad43 ffd9这种,也可以是adff d900,一开始想着保存上次解析的后两位,和下一次的前两位组合进行判断,后来又看了看原文件,发现结尾符不是ffd9就是d900,这样就可以简化算法。

起初我是把二进制文件作为字符串保存,这样理解起来比较直观,然后用char做单位进行比较,一次读5位,因为作为文本是包含空格的,

File file = new File(Environment.getExternalStorageDirectory() + "/", "ThumbJPGSegment_20.data");
FileInputStream fis = new FileInputStream(file);
InputStreamReader isr = new InputStreamReader(fis);
int n = 0;
char[] cbuf = new char[5];
char[] start = new char[] {'f', 'f', 'd', '8'};
char[] end = new char[] {'f', 'f', 'd', '9'};
char[] end2 = new char[] {'d', '9', '0', '0'};
StringBuilder stringBuilder = new StringBuilder();

boolean find = false;
boolean starts = false;

// count是用来控制log打印的,便于调试位数的错误
int count = 0;
do {
    n = isr.read(cbuf);
    // count++;
    Log.e("sss", "" + cbuf[0]);
    Log.e("sss", "" + cbuf[1]);
    Log.e("sss", "" + cbuf[2]);
    Log.e("sss", "" + cbuf[3]);
    Log.e("sss", "cbuf[0]=='f' " + (cbuf[0] == 'f'));
    if (n != -1) {
        if (!starts) {
            // Log.e("sss", "not start");
            if (cbuf[0] == start[0] && cbuf[1] == start[1] && cbuf[2] == start[2] && cbuf[3] == start[3]) {
                Log.e("sss", "start");
                stringBuilder.append(cbuf);
                starts = true;
                // last = cbuf;
            }
        } else {
            if ((cbuf[0] == end[0] && cbuf[1] == end[1] && cbuf[2] == end[2] && cbuf[3] == end[3]) ||
                    (cbuf[0] == end2[0] && cbuf[1] == end2[1] && cbuf[2] == end2[2]
                             && cbuf[3] == end2[3])) {
                stringBuilder.append(cbuf);
                Log.e("sss", stringBuilder.toString());
                find = true;
                starts = false;

                File file1 = new File(Environment.getExternalStorageDirectory() + "/", "a.jpg");
                if (!file1.exists()) {
                    file1.createNewFile();
                }

                FileOutputStream fos = new FileOutputStream(file1);
                BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(fos));
                bw.write(stringBuilder.toString());
                bw.close();

                //                            byte bt[] = stringBuilder.toString().getBytes();
                //                            try {
                //                                FileOutputStream in = new FileOutputStream(file1);
                //                                in.write(bt, 0, bt.length);
                //                                in.close();
                //                                Log.e("sss", "写入文件成功");
                //                            } catch (Exception e) {
                //                                e.printStackTrace();
                //                            }
            } else {
                stringBuilder.append(cbuf);
            }
        }
    }
} while (n != -1 && !find); // &&count<5

这样的问题是需要先把缩略图数据文件转成txt,然后用char数组读取,char比较需要一位一位比,判断上是方便了,但是最终生成的文件其实是个字符串文件,内容是ffd8 3321 6555这种,转成二进制格式就不是图片了,并且考虑到前面还要做处理就想着换成读byte的方式读取原始文件。

那么依然以二进制的方式来读取,用DataInputStream,一次读2位,先把前5个数打印出来是类似0 0 32 40 6这样的展示,那么就来判断开头是否等于ff和d8,此时遇到了一个问题,16进制的ff转成10进制是255,如果直接判断byte数字等于255会显示越界,byte的范围是-128~127,所以ff先变成二进制数11111111,第一个1是符号表示负数,后面7个1按位取反再加1,也就是求补码的规则,ff是-1,同理d8是-40,d9是-39,那么接下来就可以写代码了。

FileInputStream fis = new FileInputStream(file);
DataInputStream dis = new DataInputStream(fis);
int n = 0;
byte[] cbuf = new byte[2];
int count = 1;
do {
    if (n != -1) {
        File destDir = new File(Environment.getExternalStorageDirectory() + "/输出/" + num + "/");
        if (!destDir.exists()) {
            destDir.mkdirs();
        }
        File destFile =
                new File(destDir.getAbsolutePath(), count + ".jpg");
        if (!destFile.exists()) {
            destFile.createNewFile();
        }
        FileOutputStream in = new FileOutputStream(destFile);

        boolean find = false;
        boolean starts = false;

        do {
            n = dis.read(cbuf);

            //                Log.e("sss",""+cbuf[0]);
            //                Log.e("sss",""+cbuf[1]);

            if (!starts) {
                // 补码 byte
                if (cbuf[0] == -1 && cbuf[1] == -40) {
                    Log.e("sss", "start");
                    //                            stringBuilder.append(cbuf);
                    //                            end[count] = cbuf[0];
                    //                            end[count + 1] = cbuf[1];
                    in.write(cbuf, 0, cbuf.length);
                    starts = true;
                    //                            last = cbuf;
                }
            } else {
                // 二进制转成10进制,不是16
                if ((cbuf[0] == -1 && cbuf[1] == -39) ||
                        (cbuf[0] == -39 && cbuf[1] == 0)) {
                    in.write(cbuf, 0, cbuf.length);
                    //                            end[count] = cbuf[0];
                    //                            end[count + 1] = cbuf[1];
                    //                            stringBuilder.append(cbuf);
                    //                            Log.e("sss", stringBuilder.toString());
                    find = true;
                    starts = false;

                    in.close();
                    Log.e("sss", "写入文件成功");
                } else {
                    //                            stringBuilder.append(cbuf);
                    //                            end[count] = cbuf[0];
                    //                            end[count + 1] = cbuf[1];
                    in.write(cbuf, 0, cbuf.length);
                }
            }
        } while (!find);
    }
    count++;
} while (true);

一共2个循环,外层控制整个文件的读取,内层控制单独jpg文件的生成,对于多个源文件外层可以再加个目录的遍历for循环。这里也不考虑效率,一次写2个byte,边读边写,可以优化成读到1024个再写一次,也就是注释中end数组,每次给赋值两位,然后+2。这其实是个java脚本,我在安卓上开了个子线程运行,缩略图都显示出来了。

一个20个目录,每个2000张,共计4万张缩略图,每张缩略图148*120的分辨率大约在6k,质量很差。通过缩略图查看自己究竟丢失了哪些照片,我也发现只要导入图库里的文件,都会生成缩略图,几年前那些删除的老照片也能看到。


缩略图

后续事情:

1.跟进苹果客服维修的事情。

2.维权投诉,数据属于属于Mac自己删除,非用户删除,crash日志存在,缩略图文件存在。

crash部分日志

3.计算丢失的照片:
SSD和机械硬盘不同,恢复不是纯软件可以做到的,硬件手段都很难恢复,先罗列下。

4.多渠道恢复数据:
(1)继续联系百度网盘人员
(2)从用过的手机上进行数据恢复,都是未root的手机,成功率不高
(3)其他渠道找回照片:微信,抖音,微博,zao,笔记,简书,
(4)视频截图

5.尝试继续恢复缩略图:
Segment下除了ThumbJPGSegment_20.data外,还有Thumb64Segment_20.data,我发现后者和前者几乎同一天生成,命名数字匹配,且大小是前者2倍,因此怀疑后者可能是大一倍的缩略图也就是250*250的规格,但它不符合JPG规则,目前也没有发现任何规律,全是火星文,可以排除base64加密。

6.求助于数据恢复,求助。

上一篇下一篇

猜你喜欢

热点阅读