How I downloaded all my photos from Photobucket

I used to be a photographer. Did I take good pictures? No. Did I like taking pictures? Yes. So I took lots of pictures, but none very good. I took lots of pictures at family vacations, church events, etc all the while uploading my photos to Photobucket and sharing them with the world.

But Photobucket has turned its back on us. The website is crap (it used to be good) and now I get near-daily emails about "you've gone over your storage limit". I decided to find a way to get all my photos off this garbage website and actually put them somewhere good like Google Photos.


The Manual Approach

Since the website is crap, Photobucket has not made this easy. It is not possible to download an entire album with a single click. The only download option is on a photo. Each individual photo. I have thousands of photos uploaded to Photobucket and I will not be manually clicking through every single photo and clicking "Download".

I saw a reddit post on how to do this. Unfortunately, that method didn't work for me as the photos were corrupted when they downloaded to my phone. And the couple photos I got to work didn't have the same metadata as the uploaded ones (the "Date Taken" property wasn't preserved when downloading with my phone). Turns out not only the website is crap but also the Photobucket app.


The Automated Approach

As a programmer, my next thought was to try the API. Photobucket has an API but they tell you to go to http://developer.photobucket.com/ to get an API key and that just redirects back to your profile page (see "crap website" above). I emailed support for an API key and "Unfortunately, at this time we are not allowing outside parties to access the Photobucket API."

Ok, API is out. That leaves two approaches left: custom program using download links or autoclicker. See, my photos were taken with a DSLR and the filenames are almost always in the form "IMG_####.jpg" (where #### is a four-digit number). If the url for an image on Photobucket is like this:

http://s827.photobucket.com/user/derekantrican/media/Peru%20Trip/IMG_6055.jpg.html?sort=9&o=0

then its download link is like this:

http://s827.photobucket.com/component/Download-File?file=%2Falbums%2Fzz195%2Fderekantrican%2FPeru%20Trip%2FIMG_6055.jpg

(I am not sure if the "zz195" is unique to everyone but I found that it was the same value across all my photos and didn't change from album to album.)

Fun fact, if you open the source code of the image page (right-click > "View page source") there is actually a value for download url but it doesn't work:

"downloadUrl":"http:\/\/i827.photobucket.com\/download-albums\/zz195\/derekantrican\/Peru Trip\/IMG_6055.jpg~original"

So essentially I could sort my album by filename (something that the website can do), get the first #### image value, then make a program that counts up from that value, modifies the download url, and attempts to use that to download an image. The problem is that this url is not a publicly-facing url. Even if your album is public, you can not use the above download url unless you are logged into Photobucket. So my program would have to log in to Photobucket first. While that is possible with libraries like Selenium, I wanted a simpler approach.

So we're left with an autoclicker. I started to build one myself but after a small amount of Googling, I found Auto Mouse Click and it's exactly what I wanted.



I just need 3 actions: click on the download link, wait a little bit for the save file dialog to open up, click "Save", then press the right arrow key to go to the next image. So for each album I want to download, the workflow goes like this:
  1. Open the album on Photobucket's website
  2. Sort by filename
  3. Create a folder for the photos on your computer
  4. Click the first photo on Photobucket and download to that new folder (this will set that folder as the default download folder for future downloads)
  5. Open Auto Mouse Click and set the Repeat Count to the number of photos in the album (unfortunately, Photobucket's website yet again is crap and if you have over 1k photos in your album, it rounds to the nearest hundred photos. So I have to manually calculate the number of photos) minus one (because we downloaded the first photo in step 4)
  6. Click Start
(Here's a video where I show you how to set up Auto Mouse Click: https://youtu.be/PUmfAlOOYRU)

Theoretically, that should be it, right? Well unfortunately there are 2 problems left: the website is crap and the website is crap.

Basically sometimes you get an error screen (I got this about once every 100 downloads):


Which means you need to stop Auto Mouse Click (don't forget to set that "Start/Stop Script Execution" keyboard shortcut!), manually go back to the previous page, and start it again.

And sometimes the photo doesn't download correctly (the file is corrupted) or Auto Mouse Click misses the file altogether (maybe the website didn't respond in time, maybe AMC missed the click, who knows). So I wrote some short code to try to guess which photos got missed and also which are corrupted: https://github.com/derekantrican/MissingFilenames (worth noting that the "guess which photos got missed" part is very unique to my situation that I explained above: my photos follow the convenient naming of "IMG_####.jpg").

So now I follow all the steps above, then run my program a couple times and manually download any missed photos.

The most simple way to implement settings in a C# Windows application

I've written a number of small Windows applications (usually using WinForms, but occasionally WPF). In developing an application for users you eventually get to the point where you want to save a user's settings. Most of the time, this means writing to a file somewhere.

By Googling "winforms settings" (or some other similar query) you'll quickly find out that there's a built-in way to do this that is pretty simple. Once you set up a setting, you can simply call it throughout your project by using Properties.Settings.Default.[SettingName]

Now here are the reasons why I'm proposing something different:
  • Sometimes you need to be able to change the settings by modifying the settings file itself (rather than through the program's UI). This could be because a certain setting is causing the program to crash before it opens, it will lead to unwanted behavior upon launch, etc
  • By creating your own, custom settings file you can choose where it will be saved. This leads to a great deal of flexibility, but also allows you to jump into creating a folder for your program in the user's AppData folder (where you can start putting other things like log files, credentials, temporary files, etc)
  • A custom settings file leads to easier sharing of settings between two different computers (if you want to make sure two computers have the same settings)
  • Creating a new setting is slightly faster with my implementation

To implement mine, you can simply add a new .cs file to your project like this (Remember: change "namespace TestProject" to your appropriate project's namespace):

 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
using System;
using System.IO;
using System.Xml.Serialization;

namespace TestProject
{
    public class Settings
    {
 private static string applicationName = "Your Application";
 private static string userSettings = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), applicationName);
        private static string serializationPath = Path.Combine(UserSettings, "Settings.xml");

        #region Intialize settings with default values
        public bool Test = false;
        public string AnotherSetting = "this setting";
        public double ADouble = 5.3;
        public static Settings Instance = new Settings();
        #endregion Intialize settings with default values
  
        public static void SaveSettings()
        {
            TextWriter writer = new StreamWriter(serializationPath);
            XmlSerializer xmlSerializer = new XmlSerializer(typeof(Settings));
            xmlSerializer.Serialize(writer, Instance);
            writer.Close();
        }

        public static void ReadSettings()
        {
            if (File.Exists(serializationPath))
            {
                FileStream fileStream = new FileStream(serializationPath, FileMode.Open);
                XmlSerializer xmlSerializer = new XmlSerializer(typeof(Settings));
                try { Instance = (Settings)xmlSerializer.Deserialize(fileStream); }
                catch { }
            }
        }
    }
}

So there's a few things to cover here. We'll go through this chunk by chunk.

First you'll notice lines 14-16 that define where our settings file is going to be saved. You can condense these into one line if you like or even change it up altogether. But this is the route I like to take (obviously change "Your Application" to fit the name that you are going to use for your application folder).

Next we have lines 19-22 that define our settings. This is where you'll make the majority of your changes. The great part about this is in order to add a setting, you just have to create another variable for it here (and initialize it with a default value). As examples, I have a bool, string, and double setting. Yes, it is necessary to keep the "Instance" variable - we'll get to that in a second.

Finally, we have the rest of the file: the SaveSettings() and ReadSettings() methods (you can rename these if you like). The purpose of these is to save your settings to a file or to read the settings from a file. Ideally, one of the first things your program does would be to call the ReadSettings method and get the user's settings. And every time a setting gets changed (and you also want that change reflected in the file) you'll call SaveSettings. Pretty simple to use!

The great thing about this method is that it uses the XmlSerializer class. XmlSerializer is great because it is type-independent. Meaning that the resulting Settings.xml for our file here will look like this:


1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>
<Settings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Test>false</Test>
  <AnotherSetting>this setting</AnotherSetting>
  <ADouble>5.3</ADouble>
</Settings>

That's it! All of it! You may notice that nowhere does the xml state that "Test" is a bool or "AnotherSetting" is a string. The values are simply stored plainly and the type cast happens when ReadSettings is called. Speaking of which, here are examples of how you would use this:


1
2
3
4
5
Settings.ReadSettings(); //Read the settings from xml file
Settings.Instance.Test = true; //Set "Test" to true
Settings.Instance.AnotherSetting = "hi"; //Set "AnotherSetting to "hi"
Settings.Instance.ADouble *= 5; //Set "ADouble" to 5x its current value
Settings.SaveSettings(); //Save the settings to xml file

Notice that you have to use "Instance" when reading/writing a particular setting's value. This "Instance" is required for the XmlSerializer to work. (You could just call Settings.Test for example, but you would only get the default value - if you try to write to Settings.Test it won't get saved to the xml).

As a final note, notice the try{} catch{} in the ReadSettings method. This is in case one of the settings in the xml gets set to the wrong type (e.g. <ADouble>hi</ADouble>). This isn't a problem in the xml, but throws an exception when the XmlSerializer tries to type cast it back to a double. You can put some sort of log call, Console.WriteLine, etc in the catch{} and even add a finally{} with some different settings set. But as the code stands right now, it will simply fail to overwrite the Instance and instead the Instance will be set to its default value (which is a newly-initialized version of this class, containing the default values for all settings).

Hope this helps you out! Please let me know if you have any questions

Using the Google Calendar API to edit recurring events alongside regular events

I've been using the Google Calendar API a lot for my program Bulk Edit Calendar Events. Here's something I learned today when trying to edit recurring events:

Let's say you have recurring events like this:

  • Event 1 (June 1st)
  • Event 2 (June 2nd)
  • Event 3 (June 3rd)
(It's a single event that repeats every day until there are 3 of itself)

There are two ways to edit recurring events: as individual events or as a single event.

As a single event

Google has one recommendation for how to do this: basically, when you are doing your Events List request you should set "singleEvents" to false (which it is by default). Note that here, "singleEvents" refers to "instances" (whereas I'm using it to refer to the original recurring event). Important note: if "singleEvents" is false, "orderBy" cannot be set to "startTime". My guess for why is that if a recurring event is shown as one event, it is "fuzzy" how it should be ordered with other events if all of its instances have different start times.

I have a different recommendation. Since I am editing both regular events and recurring events at the same time, I give the user the option how they want to edit the recurring events (it's a setting in my program): again, as individual events or as a single event.

So I simply get a list of events (with "singleEvents" set to "true" and "orderBy" set to "startTime". I think this makes the most sense to the user as this is what is shown in their calendar) and loop through that list of events. When I come across a recurring event (checked by !string.IsNullOrEmpty(event.recurringEventId)) and I want to edit recurring events as one single event, I call a Events Get request on that recurringEventId (this gives me the original recurring event).  Then I use that resulting event to replace the current event in the loop. I also store that recurringEventId for later in a list so that when I loop through the other instances of that event that the user selected, I can simply skip them.

As individual events

Obviously this method breaks up the events so that they are separated from the original series (but sometimes that is what is desired).

Here we're going to skip the "Events Get" request and simply change some properties of the event. We're going to set the event's recurringEventId to "" (empty string) and set the event's originalStartTime to null. These changes are what break the recurrence, but they are necessary in order to not hit exceptions in some of the other API requests (especially when we're calling Events Update on the event later).


With these two methods we can simply loop through a list of events (including all the instances of a recurring event) and still editing recurring events as the user pleases!

Hope this helps someone out!

double.NaN == double.NaN will never be true

Something I came across recently was this oddity:

double.NaN == double.NaN will never be true.

Basically, according to wikipedia, "Not a Number" will never equal anything - not even itself. This is an interesting take on the idea of "nothing" or "unassigned" since null == null returns true. So, as a solution, we have been given double.IsNaN(double)

#FunFact

How I downloaded all my photos from Photobucket

I used to be a photographer. Did I take good pictures? No. Did I like taking pictures? Yes. So I took lots of pictures, but none very good. ...