merge-exports.sh 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. #!/bin/bash
  2. set -euo pipefail
  3. if [ $# -eq 0 ]; then
  4. echo "Usage: $0 <export-file1.json> [export-file2.json] ..." >&2
  5. exit 1
  6. fi
  7. jq -n --slurpfile files <(
  8. for file in "$@"; do
  9. basename=$(basename "$file")
  10. date_prefix=$(echo "$basename" | grep -oE '^[0-9]{4}-[0-9]{2}-[0-9]{2}' || echo "0000-00-00")
  11. jq --arg file "$file" --arg date "$date_prefix" '. + {sourceFile: $file, fileDate: $date}' "$file"
  12. done | jq -s '.'
  13. ) '
  14. # Sort files by date (later dates have priority for tie-breaking)
  15. ($files[0] | sort_by(.fileDate)) as $sorted_files |
  16. # Merge playlists
  17. ($sorted_files | map(.playlists // {} | to_entries) | flatten | group_by(.key) | map({
  18. key: .[0].key,
  19. value: (
  20. # Get the playlist name
  21. .[0].key as $playlist_name |
  22. # Collect all videos from all versions with metadata
  23. (map(.value) | flatten | unique_by(.url)) as $all_videos |
  24. # For each video, find its earliest position
  25. $all_videos | map(. as $video |
  26. # Find earliest position across all files
  27. ([$sorted_files[] |
  28. select(.playlists[$playlist_name]) |
  29. .playlists[$playlist_name] |
  30. to_entries[] |
  31. select(.value.url == $video.url) |
  32. {index: .key, date: .}
  33. ] |
  34. if length > 0 then
  35. min_by(.date)
  36. else
  37. {index: 999999, date: {fileDate: "9999-99-99"}}
  38. end) as $earliest |
  39. # Get latest video data
  40. ([$sorted_files[] |
  41. select(.playlists[$playlist_name]) |
  42. .playlists[$playlist_name][] |
  43. select(.url == $video.url)
  44. ] | last) as $latest_video |
  45. $latest_video + {
  46. _sort_date: ($sorted_files | map(select(.playlists[$playlist_name] and (.playlists[$playlist_name] | map(.url) | contains([$video.url])))) | .[0].fileDate),
  47. _sort_index: $earliest.index
  48. }
  49. ) |
  50. # Sort by earliest appearance
  51. sort_by([._sort_date, ._sort_index]) |
  52. # Remove sort keys
  53. map(del(._sort_date, ._sort_index))
  54. )
  55. }) | from_entries) as $merged_playlists |
  56. # Merge playback history
  57. ($sorted_files | map(.playbackHistory // {} | to_entries) | flatten | group_by(.key) | map({
  58. key: .[0].key,
  59. value: (
  60. # For each video in history, merge all events and metadata
  61. map(.value) |
  62. {
  63. url: (map(.url) | last),
  64. title: (map(.title) | last),
  65. tags: (map(.tags // []) | flatten | unique),
  66. history: (map(.history // []) | flatten | unique_by(.timestamp) | sort_by(.timestamp))
  67. }
  68. )
  69. }) | from_entries) as $merged_history |
  70. # Collect all open tabs (unique by URL, keep latest title)
  71. ($sorted_files | map(.openTabs // []) | flatten | group_by(.url) | map(last)) as $merged_tabs |
  72. # Output merged result
  73. {
  74. playlists: $merged_playlists,
  75. playbackHistory: $merged_history,
  76. openTabs: $merged_tabs,
  77. exportDate: (now | todate),
  78. mergedFrom: ($sorted_files | map({file: .sourceFile, date: .fileDate}))
  79. }
  80. '