美文网首页
照片被Mac删除以及提取缩略图

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

作者: 健身营养爱好者 | 来源:发表于2020-03-13 16:39 被阅读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.求助于数据恢复,求助。

    相关文章

      网友评论

          本文标题:照片被Mac删除以及提取缩略图

          本文链接:https://www.haomeiwen.com/subject/tbulshtx.html