65.9K
CodeProject 正在变化。 阅读更多。
Home

Google Takeout 图片下载器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.20/5 (6投票s)

2017年12月28日

CPOL

6分钟阅读

viewsIcon

41769

downloadIcon

1925

此实用程序从下载的 JSON 文件中下载 Google Takeout 图片。

更新

请注意,截至 2020 年 1 月 13 日,本文描述的应用程序无法工作,因为 Google 已从 Takeout JSON 文件中删除了文件链接。

如果您想开发任何基于进度条的应用程序,可以参考源代码。

引言

Google 为用户提供了一种通过 Google TakeOut 下载 Google 相册数据的方式。用户可以下载一个包含所有(真的所有吗?)照片的 zip 文件。问题在于 Google 选择性地用一个 Json 文件替换了 zip 文件中的一个图像文件。这个 json 文件包含实际图像所在位置的链接。

本文描述的实用程序将一个已提取的 Takeout 文件夹作为输入,并将所有真实图像下载到输出文件夹中。

背景

几天前,我妻子的 Google 帐户突然停止工作。经过调查,我发现她已经用完了所有 15GB 的数据。罪魁祸首主要是图像文件。她的 Android 手机正在将手机上的每一张图像同步到 Google 相册。

这给我留下了两个选择:

  1. 选择从 Google 购买额外的付费存储空间。
  2. 下载图像并释放 Google 存储空间以解锁帐户。

我选择了第二个选项。

我通过 Google TakeOut 下载了 Google 相册的 zip 文件,以为下载完 zip 文件就大功告成了。但解压后,我发现 Google 压缩的是 json 文件(其中包含指向实际图像文件的链接),而不是所有图像文件。

为了解决这个问题,我开发了一个实用程序,它可以读取所有这些 json 文件并下载相应的图像。

使用应用程序

在使用此应用程序之前,您需要:

  1. 从 Google Takeout 下载并解压 takeout zip 文件。

    更多信息可在此处找到。

  2. 解压 zip 文件后,打开演示应用程序。
  3. 选择解压后的目录作为输入目录。
  4. 选择一个输出目录,作为您需要下载图像的位置。
  5. 点击“开始下载”。

现在,请坐下来放松。应用程序会将所有图像下载到您的输出文件夹中。

关于源代码

演示应用程序中只添加了三个类:

  1. DownloadManager:执行实际下载任务。
  2. IDownloadProgressNotifier:一个接口,用于向 UI 提供下载进度信息。
  3. Form 类:用于显示 UI。它实现了 IDownloadProgressNotifier 接口。

下载管理器

下载管理器执行以下实际工作:

  1. 从提取的文件夹中读取 json 文件以获取图片链接
  2. 从 json 文件中的链接下载图像文件

我们感兴趣的 json 文件字段有:

  1. url:它是实际图像所在位置的链接。
  2. title:图像的标题。我们将以这个名称在本地保存图像。

json 片段如下所示:

{
  "title": "IMG_20110711_192757772.jpg",
  "description": "",
  "url": "https://lh3.googleusercontent.com/-JHYN2OSYh21s/Vaxpt7adEp2I/
         AAAAAAAABCu0/hIlcgO9TzwwkEJm2eQ9PcBu2rL1kPOqZWqwCLABGAYYCw/
         s0-d/IMG_20110711_192757772.jpg",
  "imageViews": "0",

使用下载管理器

要使用下载管理器,您需要通过向其构造函数提供以下参数来创建下载管理器对象。

  1. 输入目录
  2. 输出目录
  3. 一个标志,用于指示是否在出错时继续
  4. 下载进度通知处理程序
DownloadManager downloader = new DownloadManager(srcPath, dstPath, true, false, this);

现在调用 StartDownload()。这将开始下载进度。下载进度将通知给通知处理程序,该处理程序可用于更新 UI。

downloader.StartDownload();

下载管理器内部

下载管理器只有一个 public 函数。

public void StartDownload()

StartDownload() 函数

此函数将是所有处理的起点。它执行以下操作:

  1. 它调用 PreDownload 函数,通知 UI 下载过程即将开始。
                if (Notifier != null)
                    Notifier.PreDownload(TotalFilesCount);
  2. 调用 CreateDirectory 以创建输出根目录。
                //
                //Create Folder structure in the output directory.
                //
    
                CreateDirectory(new DirectoryInfo(Path.GetDirectoryName(inputRootFolder)));
  3. 调用 TraverseFolderTree 遍历 inputFolder 及其子文件夹中的所有文件。
                TraverseFolderTree(inputRootFolder);
  4. 函数 TraverseFolderTree 在完成所有文件下载后返回。完成后,通过调用通知器函数 OnDownloadFinish() 通知 UI。
                if (Notifier != null)
                    Notifier.OnDownloadFinish();

TraverseFolderTree 函数

此函数遍历输入目录树结构。它递归地遍历输入目录结构,以检查所有扩展名为“*.jpg.json”的文件。我们只对这些文件感兴趣。

首先,我们需要创建 outputfolder 路径。inputfolderoutputfolder 的目录结构将保持不变。

示例:如果用户选择以下目录作为输入和输出目录。

  • 输入目录C:\Users\test\Desktop\TakeoutProject\Input
  • 输出目录C:\Users\test\Desktop\TakeoutProject\Output

如果当前输入处理文件夹是 C:\Users\test\Desktop\TakeoutProject\Input\2015-07-17,则通过将输入根目录前缀替换为输出根目录来创建输出目录名称。

当前的 Output 目录将是 C:\Users\test\Desktop\TakeoutProject\Output\2015-07-17

String sCurrentOutputFolder = sSeedFolder.Replace(inputRootFolder, outputRootFolder) ;

创建输出目录,图像文件将下载到该目录。

Directory.CreateDirectory(sCurrentOutputFolder);

现在,枚举 currentInputDirectory 中所有扩展名为“*.jpg.json”的文件。这些文件是我们下载的候选文件。

            //
            // Now go through all files. Search for jpg link files only. 
            // "*.jpg.json" pattern will exclude other files like metadata.json
            //
            foreach (String strFile in Directory.EnumerateFiles
                            (sSeedFolder, JSON_JPEG_FILE_FILTER))
            {
                    ....
                    ....
                    ....
            }

每个文件路径都传递给 RetrieveImage 函数,以读取文件并下载实际图像文件。

 RetrieveImage(strFile, sCurrentOutputFolder);

通过调用通知器函数 OnCurrentFileProgress 通知 UI。如果发生错误,通过调用通知器函数 OnError 通知 UI。

最终的循环如下所示:

            //
            // Now go through all files. Search for jpg link files only. 
            // "*.jpg.json" pattern will exclude other files like metadata.json
            //
            foreach (String strFile in Directory.EnumerateFiles
                    (sSeedFolder, JSON_JPEG_FILE_FILTER))
            {
                try
                {
                    if (Notifier != null)
                        Notifier.OnCurrentFileProgress
                        (strFile, ProcessedFilesCount, TotalFilesCount);
                    
                    RetrieveImage(strFile, sCurrentOutputFolder);
                    ProcessedFilesCount++;
                }
                catch (Exception exc)
                {
                    if (Notifier != null)
                        Notifier.OnError(strFile, exc.Message);

                    if (stopOnError)
                    {
                        //
                        // We are done as we are supposed to stop on error.
                        //

                        throw exc;
                    }
                }
            }

现在递归遍历所有剩余的目录/子目录。

            //
            // Traverse through all directories recursively.
            //
            foreach (String strDirectory in Directory.EnumerateDirectories(sSeedFolder))
            {
                TraverseFolderTree(strDirectory);
            }

RetrieveImage 函数

此函数下载 .jpeg.json 文件并读取其内容以获取实际图像文件位置。

private void RetrieveImage(String sJsonFilePath, String sImageFilePathOutputFolder)
        {
            JsonValue fullJson = JsonValue.Parse(System.IO.File.ReadAllText(sJsonFilePath));
            String Title = fullJson["title"];
            String url = fullJson["url"];

            using (WebClient client = new WebClient())
            {
                client.DownloadFile(url, sImageFilePathOutputFolder + 
                                    Path.DirectorySeparatorChar + Title);
            }
        }

如前所述,我们只对“title”和“url”字段感兴趣。

url”表示文件的实际网络位置。WebClient 类用于从这个 URL 下载图像文件。输出文件名将与“title”相同。

using (WebClient client = new WebClient())
{
    client.DownloadFile(url, sImageFilePathOutputFolder + Path.DirectorySeparatorChar + Title);
}

IDownloadProgressNotifier 接口

interface 声明了以下函数。下载器管理器使用此接口来通知下载状态。

接口函数

OnCurrentFileProgress(String sCurrentFileName, int processedFilesCount, int totalFileCount)

此函数通知处理程序当前正在处理的文件以及总进度。

PreDownload(int TotalFilesCount)

此函数将在下载开始前调用。

OnDownloadFinish();

此函数将在下载完成后调用。

OnError(String sFileName, String sErrorInformation);

如果发生任何错误,将调用此函数。它传递发生错误的文件名以及错误信息。

Form1 类

此类是一个简单的 C# 窗体类,拥有 UI。除了图片中显示的简单 UI 元素外,它还实现了 IDownloadProgressNotifier 接口。此类的引用作为处理程序发送给下载管理器。IDownloadProgressNotifier 接口函数的实现用于使用进度状态更新 UI。

IDownloadProgressNotifier 接口实现

OnCurrentFileProgress(String sCurrentFileName, int processedFilesCount, int totalFileCount)

此函数通知处理程序当前正在处理的文件以及总进度。此回调函数将 progressbar 递增一。这使得 progressbar 向前移动一个单位。

  public void OnCurrentFileProgress
     (string sCurrentFileName, int processedFilesCount, int totalFileCount)
        {
            pbTotalProgress.Increment(1);
        }
PreDownload(int TotalFilesCount)

此回调函数用于初始化 UI 上的进度条。

 public void PreDownload(int TotalFilesCount)
        {
            pbTotalProgress.Minimum = 0;
            pbTotalProgress.Maximum = TotalFilesCount;
            pbTotalProgress.Step = 1;
        }

此函数将在下载开始前调用。

OnDownloadFinish();

此函数将在下载完成后调用。此函数用于显示进程完成消息框。

 public void OnDownloadFinish()
        {
            pbTotalProgress.Value = pbTotalProgress.Maximum;
            MessageBox.Show("Process finished.", "Success");
        }
OnError(String sFileName, String sErrorInformation);

此回调函数用于显示发生的错误。

public void OnError(string sFileName, string sErrorInformation)
        {
            MessageBox.Show("Process interrupted with an error 
                             while processing file \n File Name :" + 
                             sFileName +"\n ErrorInformation" + sErrorInformation, "Error" );
        }

历史

  • 第一版 (1)
  • 第二版:添加代码演练
© . All rights reserved.