mpdhh.sh (18070B)
1 #!/bin/sh 2 3 # Important customization: 4 mpdhh_bin=~/progs/mpdhh 5 proj_bin="${HOME}/proj/example.com" 6 publish_sh="publish.sh" 7 8 # All else, modify to your liking 9 artists="artists" 10 tracks="tracks" 11 index_md_name="_index.html" 12 music_bin_proj_local="content/music" 13 music_bin="${proj_bin}/${music_bin_proj_local}" 14 db_bin="${music_bin}/db" 15 historia_bin="${music_bin}/historia" 16 db_index_file="${historia_bin}/db.index" 17 rss_index_name="rss.index" 18 rss_index="${music_bin}/${rss_index_name}" 19 20 analysis_bin="${music_bin}/analysis" 21 analysis_artists_bin="${analysis_bin}/${artists}" 22 analysis_tracks_bin="${analysis_bin}/${tracks}" 23 analysis_tracks_db="${analysis_tracks_bin}/${tracks}.db" 24 analysis_artists_db="${analysis_artists_bin}/${artists}.db" 25 analysis_tracks_html="${analysis_tracks_bin}/${index_md_name}" 26 analysis_artists_html="${analysis_artists_bin}/${index_md_name}" 27 analysis_tracks_byartist_html="${analysis_tracks_bin}/by-artist/${index_md_name}" 28 analysis_tracks_bytrack_html="${analysis_tracks_bin}/by-track/${index_md_name}" 29 analysis_tracks_bycount_html="${analysis_tracks_bin}/by-play-count/${index_md_name}" 30 analysis_tracks_byinitial_html="${analysis_tracks_bin}/by-initial/${index_md_name}" 31 analysis_tracks_byrecent_html="${analysis_tracks_bin}/by-recent/${index_md_name}" 32 analysis_artists_byartist_html="${analysis_artists_bin}/by-artist/${index_md_name}" 33 analysis_artists_bycount_html="${analysis_artists_bin}/by-play-count/${index_md_name}" 34 analysis_artists_byinitial_html="${analysis_artists_bin}/by-initial/${index_md_name}" 35 analysis_artists_byrecent_html="${analysis_artists_bin}/by-recent/${index_md_name}" 36 th_playlist="Time::Title::Album::Artist" 37 th_analysis_tracks="Title::Album::Artist::Initial::Recent::+" 38 th_analysis_artists="Artist::Initial::Recent::+" 39 analysis_byinitial_title="By Initial" 40 analysis_byrecent_title="By Recent" 41 analysis_byartist_title="By Artist" 42 analysis_bytrack_title="By Track" 43 analysis_bycount_title="By Play Count" 44 analysis_tracks_title="Tracks" 45 analysis_artists_title="Artists" 46 47 commit_msg="dojo music update" 48 music_description="The sweet sounds from my pinephone" 49 rss_title="Sonus" 50 51 # Set rss_data_file to data file to force hugo to parse data file for rss stream 52 # for currently playing playlist page. This is unset in awk for historia page creation 53 rss_data_file="${rss_index_name}" 54 55 awk_prog_1="${mpdhh_bin}/a_write_html.awk" 56 awk_prog_2="${mpdhh_bin}/a_filter_track_data.awk" 57 playlist_html="${mpdhh_bin}/h_playlist_tmpl.md" 58 playlist_empty_html="${mpdhh_bin}/h_playlist_empty_tmpl.md" 59 analysis_tracks_tmpl="${mpdhh_bin}/h_analysis_${tracks}_tmpl.md" 60 analysis_artists_tmpl="${mpdhh_bin}/h_analysis_${artists}_tmpl.md" 61 62 page_header_html='Recently Played<span class="delimiter"> > </span>' 63 dash="-" 64 65 # Track meta (tab delim (important)) 66 formatstr='[%title%|%name%] [%album%] [%artist%|%albumartist%|%composer%]' 67 68 # 4 modes: 69 # 0 > default - update data [ & site ] 70 # 1 > stdout current track 71 # 2 > update historia only 72 # 3 > update analysis only 73 # 4 > build bin tree 74 mode="${1:-0}" 75 76 [ -z "$@" ] && tput civis 77 cleanup(){ tput cnorm; exit 0; } 78 trap cleanup SIGINT 79 80 update_historia(){ 81 82 # Manage historia files 83 [ ! -d "$historia_bin" ] && printf "Historia bin no existo, create first, then try again.\n" && exit 0 84 85 while IFS= read -r date_day; do 86 87 printf "Historia: today: %s, index date: %s\n" "$today_day" "$date_day" 88 # If today's date, skip as it is not yet history 89 [ "$today_day" = "$date_day" ] && continue 90 91 # if exists, stop updating through historia 92 [ -d "${historia_bin}/${date_day}" ] && break; 93 94 # Get date_day date vars 95 get_date_vars "$date_day" 96 97 # Update Historia index html date 98 sed -i -e "s#^\(date: \).*\$#\1${today_date}#" "${historia_bin}/${index_md_name}" 99 100 # Write date_day historia 101 write_playlist_html "$date_day" "historia" "$played_day_h" "$played_day_h" 102 103 done <"$db_index_file" 104 } 105 106 update_db_index_file(){ 107 108 [ -z "$1" ] && return 1 109 110 # Add db file to historia index file 111 # Sed will not inject anything before line 1 112 # if there is no line 1 (empty/new file) 113 # so in this case just throw the line in there 114 if [ ! -f "$db_index_file" ] 115 then 116 printf "%s\n" "$1" > "$db_index_file" 117 118 elif [ -s "$db_index_file" ] 119 then 120 ! grep "$1" "$db_index_file" && \ 121 sed -i '1i '"$1"' 122 ' "$db_index_file" 123 fi 124 sort -r -o "$db_index_file" "$db_index_file" 125 } 126 127 update_rss(){ 128 129 # Build rss data line with iso 130 # date format for hugo parsing 131 132 [ ! -f "$rss_index" ] && \ 133 printf "%s\n" "$track_data" > "$rss_index" || \ 134 sed -i '1i '"$track_data"' 135 ' "${rss_index}" 136 } 137 138 get_matching_line_from_db(){ 139 140 # $1 track_data 141 # $2 db file 142 # $3 track data fields 143 regex=; track_match= 144 a="${3:-"2,3,4"}" 145 146 if [ -z "$1" ] || [ -z "$2" ]; then return 1; fi 147 148 # Define grep pattern from track data 149 # - cut > remove the time by cutting/using fields 2,3,4 only 150 # - sed > escape special pattern matching characters from track data 151 regex="$(printf "%s" "$1" | cut -f "$a" | sed -e 's#\([][]\)#\\\1#g')" 152 153 # Find line no of first matching track 154 # - grep > run pattern as regex (-e flag) else '-' in pattern 155 # string gets interpreted as param flag 156 track_match=$(grep -ns -m1 -e "$regex" "$2" | cut -d: -f1) 157 158 # Can't test on empty variable(if no matches)... 159 # so make it 0 if need be 160 track_match="${track_match:-0}" 161 } 162 163 get_matching_line_from_analysis_db(){ 164 165 # $1 track_data 166 # $2 db file 167 # $3 track data fields 168 169 # Get line number from db using awk 170 track_match="$(awk -vtd="$1" -vf="$3" ' 171 BEGIN{ 172 FS=OFS="\t" 173 falen=split(f,fa,",") 174 split(td,tda,FS) 175 } 176 { 177 for (i=1;i<=falen;i++){ 178 if ($i != tda[fa[i]]) { 179 # On any field that does not match, 180 # exit loop, go to next line 181 next 182 } 183 } 184 # match made, print line number then exit 185 print NR 186 exit 187 }' "$2" )" 188 189 track_match="${track_match:-0}" 190 } 191 192 update_csv(){ 193 194 # First check for existing bin 195 [ ! -d "$db_bin" ] && \ 196 printf "%s\nDirectory does not exist. Exiting.\n" "$db_bin" && exit 0 197 198 pl_file="${db_bin}/${played_day}.db" 199 200 # if playlist.db no existo, 201 # if track data not empty, 202 # - create pl and add to it 203 # update historia regardless 204 205 if [ ! -f "$pl_file" ]; then 206 if [ -n "$track_data" ]; then 207 printf "%s\n" "$track_data" > "$pl_file" 208 update_rss 209 update_db_index_file "$played_day" 210 fi 211 212 update_historia 213 214 # reset date vars to today if track_data 215 [ -n "$track_data" ] && get_date_vars && return 0 || return 1 216 217 # if playlist file not empty, 218 # if track data not empty 219 # - insert new track 220 # else if track data empty 221 # - do nothing but return 1 222 elif [ -s "$pl_file" ]; then 223 224 [ -z "$track_data" ] && return 1 225 226 get_matching_line_from_db "$track_data" "$pl_file" 227 228 # If track already last track posted (line 1) 229 # - do not add to csv to mitigate redundancy 230 [ "$track_match" -eq 1 ] && return 1 231 232 update_rss 233 sed -i '1i '"$track_data"' 234 ' "$pl_file" 235 236 return 0 237 fi 238 } 239 240 __update_analysis_sort(){ 241 242 # $1 - db_file 243 # $2 - track data fields 244 245 # Count sort fields, need this for trim off sort fields post sort 246 field_count=$(printf "%s" "$2"| awk 'BEGIN{FS=","}{print NF}') 247 248 if [ ! -f "$1" ]; then 249 # scan music db omnia, freshly populating tracks db 250 printf "Db file:\n%s\ndoes not exist.\nCreating with fresh sonus db sort...\n" "$1" 251 252 # Sort all tracks played, remove any single space or single dash lines 253 #cut -f "$2" "${db_bin}"/*.db | sort -u | sed '/^[- \t]\+$\|^$/d' > "$1" 254 255 # First awk entire playlist db into a tmpfile 256 tmpfile=$(mktemp) 257 258 #cat "$db_bin"/*db | \ 259 awk -v f="$2" ' 260 BEGIN { 261 OFS=FS="\t" 262 falen=split(f,fa,",") 263 } 264 { 265 for (i=1; i<=falen; i++) { 266 printf("%s%s", $fa[i], OFS) 267 } 268 print $0 269 270 }' "$db_bin"/*db | \ 271 sort -n | \ 272 cut -f$((field_count + 1))- \ 273 > "$tmpfile" 274 275 # Now sort the tmp file into the final db file using awk 276 awk -v f="$2" ' 277 BEGIN { 278 OFS = FS = "\t" 279 split( f, fa, ",") 280 } 281 { 282 curr = "" 283 for (i=1; i<=length(fa); i++) { 284 curr = curr $fa[i] OFS 285 } 286 if (tolower(curr) == tolower(prev)) { 287 pcount++ 288 lastdate = $1 289 } else { 290 if (NR > 0) { 291 print prev firstdate, lastdate, pcount 292 } 293 firstdate = lastdate = $1 294 pcount = 1 295 } 296 prev = curr 297 } 298 END { 299 print prev firstdate, lastdate, pcount 300 301 }' "$tmpfile" > "$1" 302 303 # Cleanup 304 rm -v "$tmpfile" 305 306 printf "Done.\n" 307 else 308 # Only add files to analysis if on mode 0 i.e. while adding to playlist db files too 309 # as the entire purpose of anaylsis is to analyze what we've played, recorded, and 310 # published to the site and related db files 311 [ "$mode" -ne 0 ] && printf "Not mode 0. exiting.\n" && return 0 312 313 # Set/check for existing data in db 314 ! get_matching_line_from_analysis_db "$track_data" "$1" "$2" \ 315 && printf "Exiting on from failed get_matching_line.\n" && exit 0 316 317 318 # If track not in db, add it 319 if [ "$track_match" -ne 0 ] 320 then 321 printf "Updating %s..." "${1##*/}" 322 323 # Get new count 324 newcount="$(sed -n "${track_match}p" "$1" | cut -f "$((field_count+3))" )" 325 newcount=$((newcount+1)) 326 sed -i "${track_match}s/\([0-9]\{4\}[-0-9T:]\{21\}\)\t\([0-9]\+\)$/${played_date}\t${newcount}/" $1 327 printf "done.\n" 328 else 329 # Track no exist, add it, then sort 330 printf "%s\n" "Adding to ${1##*/}..." 331 a="$(printf "%s" "$track_data" | cut -f "$2")" 332 if printf "%s" "$a" | grep -q -v "^[- ]$" 333 then 334 printf "%s\t%s\t%s\t%s\n" "$a" "$played_date" "$played_date" "1" >> "$1" 335 sort -n -o "$1" "$1" 336 fi 337 printf "done.\n" 338 fi 339 340 fi 341 } 342 343 update_analysis(){ 344 # First check for existing bin 345 if [ ! -d "$analysis_bin" ] || [ ! -d "$analysis_tracks_bin" ] || [ ! -d "$analysis_artists_bin" ]; then 346 printf "Analysis bins do not exist. Exiting.\n" && exit 0 347 fi 348 349 # First - update tracks db 350 __update_analysis_sort "$analysis_tracks_db" "2,3,4" 351 352 # Second - update artists db 353 __update_analysis_sort "$analysis_artists_db" "4" 354 355 # Write artists index files 356 write_analysis_html "$artists" "4,1" "$th_analysis_artists" 357 # Write tracks index files 358 write_analysis_html "$tracks" "6,1,2,3" "$th_analysis_tracks" 359 360 # Write tracks sortby's 361 # 1|track 2|album 3|artist 4|firstdate 5|lastdate 6|playcount 362 __update_analysis_sortby "$tracks" "3,2" "" "$analysis_byartist_title" "$analysis_tracks_byartist_html" 363 __update_analysis_sortby "$tracks" "1" "" "$analysis_bytrack_title" "$analysis_tracks_bytrack_html" 364 __update_analysis_sortby "$tracks" "6" "-r" "$analysis_bycount_title" "$analysis_tracks_bycount_html" 365 __update_analysis_sortby "$tracks" "4" "-r" "$analysis_byinitial_title" "$analysis_tracks_byinitial_html" "4,1,2,3" 366 __update_analysis_sortby "$tracks" "5" "-r" "$analysis_byrecent_title" "$analysis_tracks_byrecent_html" "5,1,2,3" 367 __update_analysis_sortby "$artists" "1" "" "$analysis_byartist_title" "$analysis_artists_byartist_html" 368 __update_analysis_sortby "$artists" "4" "-r" "$analysis_bycount_title" "$analysis_artists_bycount_html" 369 __update_analysis_sortby "$artists" "2" "-r" "$analysis_byinitial_title" "$analysis_artists_byinitial_html" "2,1" 370 __update_analysis_sortby "$artists" "3" "-r" "$analysis_byrecent_title" "$analysis_artists_byrecent_html" "3,1" 371 372 } 373 374 __update_analysis_sortby(){ 375 376 # $1 - artists or tracks 377 # $2 - fields to sort by 378 # $3 - sort flags (for reverse sorting) 379 # $4 - sortby title 380 # $5 - sortby html file 381 382 if [ "$1" = "$artists" ]; then 383 db_file="$analysis_artists_db" 384 th_values="$th_analysis_artists" 385 print_fields="${6:-"4,1"}" 386 387 elif [ "$1" = "$tracks" ]; then 388 db_file="$analysis_tracks_db" 389 th_values="$th_analysis_tracks" 390 print_fields="${6:-"6,1,2,3"}" 391 fi 392 393 field_count=$(printf "%s" "$2" | awk 'BEGIN{FS=","}{print NF}') 394 tmpfile="$(mktemp)" 395 396 # Add sorting fields to front for.. well sorting of course 397 # Then sort, then remove the sorting fields 398 awk -vf="$2" ' 399 400 BEGIN{ OFS=FS="\t"; fl=split(f,fa,",") } 401 { 402 for (i=1; i<=fl; i++){ 403 printf("%s%s", $fa[i], OFS) 404 } 405 print $0 406 } 407 408 ' \ 409 "$db_file" | \ 410 sort -n ${3} | \ 411 cut -f$((field_count + 1))- \ 412 > "$tmpfile" 413 414 write_analysis_html "$1" "$print_fields" "$th_values" "$tmpfile" "$4" "$5" 415 416 rm -v "$tmpfile" 417 } 418 419 write_playlist_html(){ 420 # $1 = played_day 421 # $2 = page menu 422 # $3 = menu title 423 # $4 = page title 424 # 425 # get date vars if date param ($1) 426 # set else use date vars set when 427 # new track change for current 428 # playlist (see while loop) 429 # 430 # date param will be set for 431 # historia pages, where menu ($2) and 432 # title ($3) params will also be passed 433 if [ -n "$1" ]; then 434 mkdir -p "${historia_bin}/${played_day}" || exit 0 435 md_file="${historia_bin}/${played_day}/${index_md_name}" 436 else 437 md_file="${music_bin}/${index_md_name}" 438 439 # udpate date vars if historia updated html files (dates 440 # will be historical, but we need todays) 441 [ ! "$today_date" = "$played_date" ] && get_date_vars 442 fi 443 444 datafile="${db_bin}/${played_day}.db" 445 awk_htmltmpl="$playlist_html" 446 447 [ ! -f "$datafile" ] && \ 448 datafile="" && \ 449 awk_htmltmpl="$playlist_empty_html" 450 451 printf "Awking html for %s..." "$played_day" 452 453 # generate html with awk 454 awk \ 455 -F "\t" \ 456 -v today_date="$today_date" \ 457 -v today_day="$today_day" \ 458 -v played_date="$played_date" \ 459 -v played_day="$played_day" \ 460 -v played_day_h="$played_day_h" \ 461 -v page_menu="${2:-"main"}" \ 462 -v page_menutitle="${3:-"Music"}" \ 463 -v page_menuidentifier="${3:-"Music"}" \ 464 -v page_title="${4:-"Music"}" \ 465 -v page_header_html="$page_header_html" \ 466 -v track_data="$track_data" \ 467 -v rss_data_file="$rss_data_file" \ 468 -v description="$music_description" \ 469 -v rss_title="$rss_title" \ 470 -v html_type="playlist" \ 471 -v pfields="" \ 472 -v th_values="" \ 473 -v tally="$(wc -l "$datafile" 2>/dev/null | cut -d" " -f1)" \ 474 -f $awk_prog_1 \ 475 "$awk_htmltmpl" "$datafile" > "$md_file" 476 477 printf "done.\n" 478 } 479 write_analysis_html(){ 480 481 # $1 tracks or artists 482 # $2 field list (comma delim'd) 483 # $3 [ table th values ( delim "::" ) ] 484 # $4 [ database file ] 485 # $5 [ title ] 486 # $6 [ output html file ] 487 488 if [ "$1" = "$tracks" ]; then 489 awk_datafile="${4:-"$analysis_tracks_db"}" 490 awk_htmltmpl="$analysis_tracks_tmpl" 491 awk_html_file="${6:-"$analysis_tracks_html"}" 492 awk_title="${5:-"$analysis_tracks_title"}" 493 elif [ "$1" = "$artists" ]; then 494 awk_datafile="${4:-"$analysis_artists_db"}" 495 awk_htmltmpl="$analysis_artists_tmpl" 496 awk_html_file="${6:-"$analysis_artists_html"}" 497 awk_title="${5:-"$analysis_artists_title"}" 498 fi 499 500 awk_menuidentifier="${1}" 501 502 printf "Awking html for %s %s..." "$1" "$awktitle" 503 504 # generate html with awk 505 awk \ 506 -F "\t" \ 507 -v today_date="$today_date" \ 508 -v today_day="$today_day" \ 509 -v played_date="$played_date" \ 510 -v played_day="$played_day" \ 511 -v played_day_h="$played_day_h" \ 512 -v page_menu="analysis" \ 513 -v page_menutitle="${awk_title}" \ 514 -v page_menuidentifier="$awk_menuidentifier" \ 515 -v page_title="${awk_title}" \ 516 -v page_header_html="$page_header_html" \ 517 -v track_data="$track_data" \ 518 -v rss_data_file="$rss_data_file" \ 519 -v description="$music_description" \ 520 -v rss_title="$rss_title" \ 521 -v html_type="analysis" \ 522 -v pfields="$2" \ 523 -v th_values="$3" \ 524 -v tally="$(<"${awk_datafile}" wc -l)" \ 525 -f $awk_prog_1 \ 526 "${awk_htmltmpl}" "${awk_datafile}" > "${awk_html_file}" 527 printf "done.\n" 528 } 529 530 publish_site(){ 531 532 # Run simple shell script that 533 # 1. Updates hugo (hugo cmd) 534 # 2. git add . 535 # 3. git commit -m "message" 536 # 4. git push 537 538 [ "$MPDHH_PUBLISH" = "n" ] && printf "Not publishing.\n" && return 539 540 # Must change to hugo project bin to run hugo command (in publish.sh) 541 cur_bin="$(pwd)" 542 cd "$proj_bin" 543 [ -f ./"${publish_sh}" ] && sh ./"${publish_sh}" "${commit_msg}" 544 cd "$cur_bin" 545 } 546 547 get_track_data(){ 548 #mpc current -f "$formatstr" 2>/dev/null | grep -v '^[a-zA-Z]*:\s*$' 549 mpc current -f "$formatstr" 2>/dev/null | \ 550 awk \ 551 -F"\t" \ 552 -v filename="$(mpc current -f '%file%' 2>/dev/null | awk -F "/" '{print $NF}')" \ 553 -v played_date="$played_date" \ 554 -v d="$dash" \ 555 -f $awk_prog_2 556 } 557 558 get_date_vars(){ 559 today_date="$(date -Iseconds)" 560 today_day="$(date --date="$today_date" +"%Y%m%d")" 561 played_date="${1:-"$today_date"}" 562 played_day="$(date --date="$played_date" +"%Y%m%d")" 563 played_day_h="$(date --date="$played_date" +"%Y %m %d | %A")" 564 } 565 566 site_git_merge(){ 567 printf "Git fetch && merge...\n" 568 cur_bin="$(pwd)" 569 cd "$proj_bin" && \ 570 git fetch && git merge 571 cd "$cur_bin" 572 } 573 574 bin_struct(){ 575 # Make the bins 576 [ ! -d "$proj_bin" ] && printf "Please create proj bin, then continue. Exiting.\n" && exit 0 577 __bin_struct "$music_bin" 578 __bin_struct "$db_bin" 579 __bin_struct "$historia_bin" 580 __bin_struct "$analysis_bin" 581 __bin_struct "$analysis_artists_bin" 582 __bin_struct "${analysis_artists_bycount_html%/*}" 583 __bin_struct "${analysis_artists_byartist_html%/*}" 584 __bin_struct "${analysis_artists_byinitial_html%/*}" 585 __bin_struct "${analysis_artists_byrecent_html%/*}" 586 __bin_struct "$analysis_tracks_bin" 587 __bin_struct "${analysis_tracks_bytrack_html%/*}" 588 __bin_struct "${analysis_tracks_byartist_html%/*}" 589 __bin_struct "${analysis_tracks_bycount_html%/*}" 590 __bin_struct "${analysis_tracks_byinitial_html%/*}" 591 __bin_struct "${analysis_tracks_byrecent_html%/*}" 592 } 593 594 __bin_struct(){ 595 [ ! -d "$1" ] && mkdir -v "$1" || printf "Bin already exists: %s\n" "$1" 596 } 597 598 while true; do 599 clear 600 601 get_date_vars 602 track_data="$(get_track_data)" 603 604 if [ "$mode" = "0" ]; then 605 # merge with site repo first 606 site_git_merge 607 # Update playlists csvs 608 # If new track then update 609 # analysis db/html files 610 update_csv && update_analysis 611 # Write playlist html files 612 write_playlist_html 613 # Publish site 614 publish_site 615 616 elif [ "$mode" = "1" ]; then 617 printf "%s\n" "$track_data" 618 break; 619 620 elif [ "$mode" = "2" ]; then 621 ls -1d "$historia_bin"/????????/ | xargs -I{} rm -rv "{}" 622 update_historia && printf "Done.\n"; exit 0 623 624 elif [ "$mode" = "3" ]; then 625 [ ! "$MPDHH_PUBLISH" = "n" ] && site_git_merge 626 # Refresh the analysis db 627 [ -f "$analysis_artists_db" ] && rm -v "$analysis_artists_db" 628 [ -f "$analysis_tracks_db" ] && rm -v "$analysis_tracks_db" 629 update_analysis && publish_site && printf "Done.\n"; exit 0 630 631 elif [ "$mode" = "4" ]; then 632 bin_struct 633 exit 0 634 fi 635 636 >/dev/null mpc idle player sticker 637 done