<?xml version="1.0" encoding="UTF-8" ?><!-- generator=Zoho Sites --><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><atom:link href="https://www.talonfab.com.au/blogs/tools/feed" rel="self" type="application/rss+xml"/><title>Talonfab Pty Ltd - Blog , Tools</title><description>Talonfab Pty Ltd - Blog , Tools</description><link>https://www.talonfab.com.au/blogs/tools</link><lastBuildDate>Sun, 22 Feb 2026 13:57:49 +1100</lastBuildDate><generator>http://zoho.com/sites/</generator><item><title><![CDATA[eBird Photographic Life List Tool]]></title><link>https://www.talonfab.com.au/blogs/post/ebird-photographic-life-list</link><description><![CDATA[<img align="left" hspace="5" src="https://www.talonfab.com.au/files/blog_photos/380 Dusky Grasswren.jpg"/>This tool gives you your photographic life list by uploading a CSV exported from the “Latest Photos” section of your eBird profile. Everything runs locally in your browser, no data is uploaded or stored, and you can update the list at any time by exporting and uploading a fresh CSV.]]></description><content:encoded><![CDATA[
<div class="zpcontent-container blogpost-container "><div data-element-id="elm_1Op1fkxaTayStjfG2EUIrg" data-element-type="section" class="zpsection "><style type="text/css"></style><div class="zpcontainer"><div data-element-id="elm_jOxYXx8aTLqhYjv9FVUShA" data-element-type="row" class="zprow zpalign-items- zpjustify-content- "><style type="text/css"></style><div data-element-id="elm_QCFQlZNsRMuSn4I1cz1CSA" data-element-type="column" class="zpelem-col zpcol-12 zpcol-md-12 zpcol-sm-12 zpalign-self- "><style type="text/css"></style><div data-element-id="elm_S3p9qWIQAOd_ri36O68JeQ" data-element-type="image" class="zpelement zpelem-image "><style> @media (min-width: 992px) { [data-element-id="elm_S3p9qWIQAOd_ri36O68JeQ"] .zpimage-container figure img { width: 800px ; height: 533.50px ; } } </style><div data-caption-color="" data-size-tablet="" data-size-mobile="" data-align="center" data-tablet-image-separate="false" data-mobile-image-separate="false" class="zpimage-container zpimage-align-center zpimage-tablet-align-center zpimage-mobile-align-center zpimage-size-large zpimage-tablet-fallback-fit zpimage-mobile-fallback-fit hb-lightbox " data-lightbox-options="
                type:fullscreen,
                theme:dark"><figure role="none" class="zpimage-data-ref"><a class="zpimage-anchor" style="cursor:pointer;" href="javascript:;"><picture><img class="zpimage zpimage-style-none zpimage-space-none " src='https://cdn4.zohoecommerce.com/files/blog_photos/380%20Dusky%20Grasswren.jpg?v=1765352023&storefront_domain=www.talonfab.com.au' size="large" alt="" data-lightbox="true"/></picture></a></figure></div>
</div><div data-element-id="elm_4syhfemlRBmwZZuoDgBBuQ" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-center " data-editor="true"><div><div style="color:inherit;text-align:left;">This tool generates a photographic life list from your eBird photo records. Simply drag/upload a CSV exported from your eBird profile and your life list will be instantly shown.&nbsp;<span style="color:inherit;text-align:center;">Everything runs entirely in your browser. The CSV is processed locally and no data is uploaded or stored. You can refresh the list at any time by uploading a new export.</span></div><div style="color:inherit;text-align:left;"><br/></div><div style="text-align:left;color:inherit;">For each species, the tool shows its life-list position, common and scientific names, the date of your first photographed record, the locality, and direct links to the original eBird checklist and your full observation history for that species. If you have photographed a species multiple times, only the earliest photographic record is used.</div></div><div style="text-align:left;color:inherit;"><br/></div><div style="text-align:left;color:inherit;">To export the CSV from eBird and use the tool, follow these steps:<br/></div><div style="text-align:left;color:inherit;"><ol><li>Go to the <a href="https://media.ebird.org/catalog?unconfirmed=incl&amp;mediaType=photo" title="eBird gallery tool" rel="">eBird gallery tool</a>.</li><li>Click &quot;Contributors&quot; (or &quot;Filter&quot; then &quot;Contributors&quot; on mobile).&nbsp;</li><li>Click on &quot;My Media&quot; (and then &quot;Show results&quot; on mobile).</li><li>Click on &quot;Export&quot;.</li><li>Use the CSV file that downloads with this tool.</li></ol><div>Limitations:<br/></div><div><ul><li>Some sensitive species are not shown in eBird galleries - these will not be included in the list.</li><li>If you have uploaded more than 10000 photos then it isn't possible to export them in the way described above - you will need to do multiple exports based on e.g. date ranges and stitch them into a single CSV prior to using this tool.</li></ul></div></div></div>
</div><div data-element-id="elm_gcx6wZ-wTlrnW_qiQ8f15A" data-element-type="codeSnippet" class="zpelement zpelem-codesnippet "><div class="zpsnippet-container"><div id="auspexr-life-tool" style="max-width:900px;margin:20px auto;font-family:system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:14px;line-height:1.4;border:1px solid rgb(221, 221, 221);border-radius:6px;padding:5px;"><h2 style="margin-top:0;margin-bottom:8px;font-size:20px;">Upload Image List</h2><p style="margin:0 0 8px 0;"> Follow the instructions above to export the photos from your eBird profile as a CSV, then upload it here to generate a photographic life list. </p><label for="auspexr-life-csv" style="display:inline-block;margin:10px 0 4px 0;font-weight:500;"> Upload eBird CSV </label><input
 type="file" id="auspexr-life-csv" accept=".csv" style="display:block;margin-bottom:8px;"/><div id="auspexr-life-controls" style="display:none;margin-bottom:8px;font-size:13px;"><span style="margin-right:6px;">Sort by:</span><button type="button" id="auspexr-sort-obs" class="auspexr-sort-btn">Observation order</button><button type="button" id="auspexr-sort-alpha" class="auspexr-sort-btn">Alphabetical</button><button type="button" id="auspexr-sort-tax" class="auspexr-sort-btn">Taxonomic</button></div>
<div id="auspexr-life-container" style="border:1px solid rgb(221, 221, 221);border-radius:6px;padding:10px;min-height:60px;"><p style="margin:0;color:rgb(85, 85, 85);"> No data loaded yet. Upload your eBird photos CSV to see your photographic life list. </p></div>
<style> #auspexr-life-tool .auspexr-row { display: grid; grid-template-columns: 50px minmax(160px, 1.7fr) 120px minmax(120px, 1.3fr) 80px; gap: 6px; align-items: center; padding: 6px 4px; border-bottom: 1px solid #eee; font-size: 13px; } #auspexr-life-tool .auspexr-row:nth-child(odd) { background: #fafafa; } #auspexr-life-tool .auspexr-header { font-weight:600; background:#f0f0f0; } #auspexr-life-tool .auspexr-rank { text-align:right; padding-right:4px; font-variant-numeric:tabular-nums; white-space:nowrap; } #auspexr-life-tool .auspexr-name { font-weight:500; } #auspexr-life-tool .auspexr-sci { display:block; font-style:italic; font-weight:400; color:#666; font-size:12px; } #auspexr-life-tool .auspexr-date a, #auspexr-life-tool .auspexr-view a { text-decoration:none; color:#1a73e8; } #auspexr-life-tool .auspexr-date a:hover, #auspexr-life-tool .auspexr-view a:hover { text-decoration:underline; } #auspexr-life-tool .auspexr-sort-btn { padding:3px 8px; margin-right:4px; border-radius:4px; border:1px solid #ccc; background:#ffffff; cursor:pointer; color:#000 !important; font-size:12px; } #auspexr-life-tool .auspexr-sort-btn:hover { background:#eee; } @media (max-width: 640px) { #auspexr-life-tool .auspexr-row { grid-template-columns: 40px 1fr; grid-template-rows: auto auto auto; } #auspexr-life-tool .auspexr-date, #auspexr-life-tool .auspexr-loc, #auspexr-life-tool .auspexr-view { font-size:12px; } #auspexr-life-tool .auspexr-date, #auspexr-life-tool .auspexr-loc, #auspexr-life-tool .auspexr-view { grid-column: 2 / 3; } #auspexr-life-tool .auspexr-header { display:none; } } </style></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.3.2/papaparse.min.js"></script><script>
(function() {
  var input = document.getElementById("auspexr-life-csv");
  var container = document.getElementById("auspexr-life-container");
  var controls = document.getElementById("auspexr-life-controls");
  var btnObs = document.getElementById("auspexr-sort-obs");
  var btnAlpha = document.getElementById("auspexr-sort-alpha");
  var btnTax = document.getElementById("auspexr-sort-tax");

  if (!input || !container || typeof Papa === "undefined") return;

  var baseList = [];
  var currentSort = "obs";
  var currentDir = "asc";

  input.addEventListener("change", handleFile, false);
  if (btnObs) btnObs.addEventListener("click", function() { setSort("obs"); });
  if (btnAlpha) btnAlpha.addEventListener("click", function() { setSort("alpha"); });
  if (btnTax) btnTax.addEventListener("click", function() { setSort("tax"); });

  function handleFile(evt) {
    var file = evt.target.files && evt.target.files[0];
    if (!file) return;
    Papa.parse(file, {
      header: true,
      skipEmptyLines: true,
      complete: function(results) {
        buildLifeList(results.data || []);
      }
    });
  }

  function sciKeyFromScientificName(sci) {
    if (!sci) return null;
    var clean = String(sci).trim();

    // Remove bracketed qualifiers so "Treron calvus [calvus Group]" still keys as "Treron calvus"
    clean = clean.replace(/\[[^\]]*\]/g, " ").replace(/\s+/g, " ").trim();

    var parts = clean.split(" ").filter(Boolean);
    if (parts.length < 2) return null;
    return parts[0] + " " + parts[1];
  }

  function tokenCountScientificName(sci) {
    if (!sci) return Number.POSITIVE_INFINITY;
    var clean = String(sci).trim().replace(/\[[^\]]*\]/g, " ").replace(/\s+/g, " ").trim();
    var parts = clean.split(" ").filter(Boolean);
    return parts.length || Number.POSITIVE_INFINITY;
  }

  function isBetterDisplay(current, candidate) {
    // Prefer entries whose scientific name is exactly two words.
    // Otherwise prefer fewer words. If tie, keep the current.
    var curCount = tokenCountScientificName(current && current.displaySciRaw);
    var candCount = tokenCountScientificName(candidate && candidate.displaySciRaw);

    var curIsTwo = curCount === 2;
    var candIsTwo = candCount === 2;

    if (candIsTwo && !curIsTwo) return true;
    if (!candIsTwo && curIsTwo) return false;

    if (candCount < curCount) return true;
    return false;
  }

  function buildLifeList(records) {
    var map = {};

    records.forEach(function(r) {
      var code = r["eBird Species Code"];
      var taxonCategory = r["Taxon Category"];
      var y = r.Year, m = r.Month, d = r.Day;

      // Filters
      if (!code || String(code).startsWith("t-")) return;
      if (taxonCategory !== "Species") return;
      if (!y || !m || !d) return;

      var sci = r["Scientific Name"];
      var key = sciKeyFromScientificName(sci);
      if (!key) return;

      var dateStr = String(y) + "-" + String(m).padStart(2, "0") + "-" + String(d).padStart(2, "0");
      var date = new Date(dateStr);

      var taxSortRaw = r["Taxonomic Sort"];
      var taxSortNum = (taxSortRaw !== undefined && taxSortRaw !== null && taxSortRaw !== "")
        ? Number(taxSortRaw)
        : NaN;

      // Initialise group
      if (!map[key]) {
        map[key] = {
          key: key,

          // Earliest photo info (used for date/checklist/locality)
          date: date,
          checklistId: r["eBird Checklist ID"],
          locality: r.Locality || "",

          // Best display info (prefer true species row)
          common: r["Common Name"] || "",
          sciDisplay: key,
          displayCode: code,
          displaySciRaw: sci,

          // Taxonomic sort for grouping (use smallest available)
          taxonomicSort: taxSortNum
        };
        return;
      }

      var g = map[key];

      // Track earliest photographed record for this species-key
      if (date < g.date) {
        g.date = date;
        g.checklistId = r["eBird Checklist ID"];
        g.locality = r.Locality || "";
      }

      // Track the "best" taxonomic sort (smallest numeric, ignoring NaN)
      if (!isNaN(taxSortNum)) {
        if (isNaN(g.taxonomicSort) || taxSortNum < g.taxonomicSort) {
          g.taxonomicSort = taxSortNum;
        }
      }

      // Prefer shorter scientific name row for display (species over subspecies)
      var candidate = {
        common: r["Common Name"] || "",
        displayCode: code,
        displaySciRaw: sci
      };
      var current = {
        displaySciRaw: g.displaySciRaw
      };

      if (isBetterDisplay(current, candidate)) {
        g.common = candidate.common;
        g.displayCode = candidate.displayCode;
        g.displaySciRaw = candidate.displaySciRaw;
        g.sciDisplay = key; // always show Genus species
      }
    });

    baseList = Object.keys(map).map(function(k) { return map[k]; });

    // Observation order based on earliest date (oldest first)
    baseList.sort(function(a, b) { return a.date - b.date; });
    baseList.forEach(function(sp, idx) {
      sp.obsIndex = idx + 1;
    });

    currentSort = "obs";
    currentDir = "asc";
    if (controls) controls.style.display = baseList.length ? "block" : "none";

    updateSortButtonLabels();
    renderList(getSortedList());
  }

  function setSort(mode) {
    if (currentSort === mode) {
      currentDir = (currentDir === "asc") ? "desc" : "asc";
    } else {
      currentSort = mode;
      currentDir = "asc";
    }
    updateSortButtonLabels();
    renderList(getSortedList());
  }

  function getSortedList() {
    var list = baseList.slice();

    if (currentSort === "alpha") {
      list.sort(function(a, b) {
        var ac = (a.common || "").toLowerCase();
        var bc = (b.common || "").toLowerCase();
        if (ac < bc) return currentDir === "asc" ? -1 : 1;
        if (ac > bc) return currentDir === "asc" ? 1 : -1;
        return a.obsIndex - b.obsIndex;
      });
    } else if (currentSort === "tax") {
      list.sort(function(a, b) {
        var at = isNaN(a.taxonomicSort) ? Number.POSITIVE_INFINITY : a.taxonomicSort;
        var bt = isNaN(b.taxonomicSort) ? Number.POSITIVE_INFINITY : b.taxonomicSort;
        if (at < bt) return currentDir === "asc" ? -1 : 1;
        if (at > bt) return currentDir === "asc" ? 1 : -1;
        return a.obsIndex - b.obsIndex;
      });
    } else {
      list.sort(function(a, b) {
        return currentDir === "asc"
          ? a.obsIndex - b.obsIndex
          : b.obsIndex - a.obsIndex;
      });
    }
    return list;
  }

  function updateSortButtonLabels() {
    if (!btnObs || !btnAlpha || !btnTax) return;
    var arrow = currentDir === "asc" ? " ▲" : " ▼";
    btnObs.textContent = "Observation order" + (currentSort === "obs" ? arrow : "");
    btnAlpha.textContent = "Alphabetical" + (currentSort === "alpha" ? arrow : "");
    btnTax.textContent = "Taxonomic" + (currentSort === "tax" ? arrow : "");
  }

  function renderList(list) {
    if (!list.length) {
      container.innerHTML = '<p style="margin:0;color:#555;">No photos found in this CSV.</p>';
      return;
    }

    var html = '';
    html += '<div class="auspexr-row auspexr-header">';
    html += '<div class="auspexr-rank">#</div>';
    html += '<div>Species</div>';
    html += '<div>Date</div>';
    html += '<div>Locality</div>';
    html += '<div>Links</div>';
    html += '</div>';

    list.forEach(function(sp) {
      var ds = sp.date.toLocaleDateString("en-GB", { day:"2-digit", month:"short", year:"numeric" });
      var chkUrl = "https://ebird.org/checklist/" + sp.checklistId;

      // Use the chosen displayCode for the "View all" link
      var lifeUrl = "https://ebird.org/lifelist?time=life&spp=" + encodeURIComponent(sp.displayCode || "");

      html += '<div class="auspexr-row">';
      html += '<div class="auspexr-rank">' + sp.obsIndex + '</div>';
      html += '<div class="auspexr-name">' +
                (sp.common || "") +
                '<span class="auspexr-sci">(' + (sp.sciDisplay || "") + ')</span>' +
              '</div>';
      html += '<div class="auspexr-date"><a href="' + chkUrl + '" target="_blank" rel="noopener noreferrer">' + ds + '</a></div>';
      html += '<div class="auspexr-loc">' + (sp.locality || "") + '</div>';
      html += '<div class="auspexr-view"><a href="' + lifeUrl + '" target="_blank" rel="noopener noreferrer">View all</a></div>';
      html += '</div>';
    });

    container.innerHTML = html;
  }
})();
</script></div>
</div></div></div></div></div></div> ]]></content:encoded><pubDate>Wed, 10 Dec 2025 18:08:46 +1100</pubDate></item></channel></rss>