|
|
@@ -0,0 +1,92 @@
|
|
|
+#!/bin/bash
|
|
|
+
|
|
|
+set -euo pipefail
|
|
|
+
|
|
|
+if [ $# -eq 0 ]; then
|
|
|
+ echo "Usage: $0 <export-file1.json> [export-file2.json] ..." >&2
|
|
|
+ exit 1
|
|
|
+fi
|
|
|
+
|
|
|
+jq -n --slurpfile files <(
|
|
|
+ for file in "$@"; do
|
|
|
+ basename=$(basename "$file")
|
|
|
+ date_prefix=$(echo "$basename" | grep -oE '^[0-9]{4}-[0-9]{2}-[0-9]{2}' || echo "0000-00-00")
|
|
|
+
|
|
|
+ jq --arg file "$file" --arg date "$date_prefix" '. + {sourceFile: $file, fileDate: $date}' "$file"
|
|
|
+ done | jq -s '.'
|
|
|
+) '
|
|
|
+# Sort files by date (later dates have priority for tie-breaking)
|
|
|
+($files[0] | sort_by(.fileDate)) as $sorted_files |
|
|
|
+
|
|
|
+# Merge playlists
|
|
|
+($sorted_files | map(.playlists // {} | to_entries) | flatten | group_by(.key) | map({
|
|
|
+ key: .[0].key,
|
|
|
+ value: (
|
|
|
+ # Get the playlist name
|
|
|
+ .[0].key as $playlist_name |
|
|
|
+
|
|
|
+ # Collect all videos from all versions with metadata
|
|
|
+ (map(.value) | flatten | unique_by(.url)) as $all_videos |
|
|
|
+
|
|
|
+ # For each video, find its earliest position
|
|
|
+ $all_videos | map(. as $video |
|
|
|
+ # Find earliest position across all files
|
|
|
+ ([$sorted_files[] |
|
|
|
+ select(.playlists[$playlist_name]) |
|
|
|
+ .playlists[$playlist_name] |
|
|
|
+ to_entries[] |
|
|
|
+ select(.value.url == $video.url) |
|
|
|
+ {index: .key, date: .}
|
|
|
+ ] |
|
|
|
+ if length > 0 then
|
|
|
+ min_by(.date)
|
|
|
+ else
|
|
|
+ {index: 999999, date: {fileDate: "9999-99-99"}}
|
|
|
+ end) as $earliest |
|
|
|
+
|
|
|
+ # Get latest video data
|
|
|
+ ([$sorted_files[] |
|
|
|
+ select(.playlists[$playlist_name]) |
|
|
|
+ .playlists[$playlist_name][] |
|
|
|
+ select(.url == $video.url)
|
|
|
+ ] | last) as $latest_video |
|
|
|
+
|
|
|
+ $latest_video + {
|
|
|
+ _sort_date: ($sorted_files | map(select(.playlists[$playlist_name] and (.playlists[$playlist_name] | map(.url) | contains([$video.url])))) | .[0].fileDate),
|
|
|
+ _sort_index: $earliest.index
|
|
|
+ }
|
|
|
+ ) |
|
|
|
+ # Sort by earliest appearance
|
|
|
+ sort_by([._sort_date, ._sort_index]) |
|
|
|
+ # Remove sort keys
|
|
|
+ map(del(._sort_date, ._sort_index))
|
|
|
+ )
|
|
|
+}) | from_entries) as $merged_playlists |
|
|
|
+
|
|
|
+# Merge playback history
|
|
|
+($sorted_files | map(.playbackHistory // {} | to_entries) | flatten | group_by(.key) | map({
|
|
|
+ key: .[0].key,
|
|
|
+ value: (
|
|
|
+ # For each video in history, merge all events and metadata
|
|
|
+ map(.value) |
|
|
|
+ {
|
|
|
+ url: (map(.url) | last),
|
|
|
+ title: (map(.title) | last),
|
|
|
+ tags: (map(.tags // []) | flatten | unique),
|
|
|
+ history: (map(.history // []) | flatten | unique_by(.timestamp) | sort_by(.timestamp))
|
|
|
+ }
|
|
|
+ )
|
|
|
+}) | from_entries) as $merged_history |
|
|
|
+
|
|
|
+# Collect all open tabs (unique by URL, keep latest title)
|
|
|
+($sorted_files | map(.openTabs // []) | flatten | group_by(.url) | map(last)) as $merged_tabs |
|
|
|
+
|
|
|
+# Output merged result
|
|
|
+{
|
|
|
+ playlists: $merged_playlists,
|
|
|
+ playbackHistory: $merged_history,
|
|
|
+ openTabs: $merged_tabs,
|
|
|
+ exportDate: (now | todate),
|
|
|
+ mergedFrom: ($sorted_files | map({file: .sourceFile, date: .fileDate}))
|
|
|
+}
|
|
|
+'
|