Implement initial audiobook UX (some of which is a bit of a WIP).

- Renamed various components and moved them to src/components.
- Renamed KOTO_PREFERRED_MODEL* to KOTO_PREFERRED_PLAYLIST*
- Renamed koto string utility functions to always be prefixed with koto_utils_string_ for consistency.
- Added configuration options for show / hiding various album information, as well as preferred sort type.
- Changed db schema to reflect various metadata changes (sorry).
- Implemented genre, narrator, year aggregation from KotoTrack to KotoAlbum for use in KotoAlbumInfo and audiobooks.
- Rearchitected our playlist functionality for KotoAlbums to always have an inner KotoPlaylist that is used.
- Added various getters / setters for new koto_album functionality.
- Implement aggregation of KotoAlbum pointer aggregation in the KotoArtist as a GQueue and GListStore instead of GList so we can get all the albums associated with an artist and use the GListStore for the audiobook view.
- Implement some initial album sorting in Artists (more work to do on this front).
- Many improvements to file indexing logic for CD and position detection, various new koto_track_helpers.
- Add new logic for knowing when to hide playlists given we generate them for each Album now.
- Fix missing updates of KotoPlaylist in KotoNav.
- Added playback position to KotoPlayerbar, renamed bar refs to self.
- New Playlist state saving.
- Updated track ticking logic for track in KotoPlaybackEngine.
- Fixed playback position detection in our KotoPlaybackEngine by swapping from GST_FORMAT_DEFAULT to GST_FORMAT_TIME.
- Changed our get_progress to divide by GST_SECOND.
- Fixed missing type checks in various KotoPlaybackEngine functions.

Fixes #13. Fixes #14. Fixes #15.
This commit is contained in:
Joshua Strobl 2021-08-10 19:18:46 +03:00
parent 93f3f45adf
commit 77b4e900e6
86 changed files with 4926 additions and 824 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

BIN
data/genres/sci-fi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
data/genres/travel.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

View file

@ -0,0 +1,181 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="520"
height="240"
version="1.1"
viewBox="0 0 137.58 63.5"
id="svg49"
sodipodi:docname="business-and-personal-finance.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview51"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="3.5480769"
inkscape:cx="308.75881"
inkscape:cy="167.13279"
inkscape:window-width="3840"
inkscape:window-height="2087"
inkscape:window-x="1920"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg49" />
<defs
id="defs15">
<linearGradient
id="linearGradient295062"
x1="-134.86"
x2="-134.86"
y1="-123.4"
y2="40.627"
gradientUnits="userSpaceOnUse">
<stop
stop-color="#5f38ad"
offset="0"
id="stop7" />
<stop
stop-color="#874ff5"
offset="1"
id="stop9" />
</linearGradient>
<clipPath
id="clipPath294187-9">
<rect
x="-218.13"
y="-82.027"
width="39.22"
height="24.317"
rx="5.2917"
ry="5.2917"
fill="#e8cc4c"
fill-rule="evenodd"
id="rect12" />
</clipPath>
</defs>
<g
id="g1680"
transform="scale(1.0000242,1)"
style="stroke-width:0.999988">
<g
id="g1664"
style="stroke-width:0.999988">
<rect
transform="translate(0,-11.25)"
x="2.7931001e-06"
y="11.25"
width="137.58"
height="63.5"
fill="#ffffff"
stroke-opacity="0.82206"
stroke-width="1.05829"
style="paint-order:stroke fill markers"
id="rect17" />
<g
transform="matrix(0.14144,0,0,0.14144,136.46,21.232)"
stroke-width="7.06981"
id="g39">
<rect
x="-267.14999"
y="-123.41"
width="264.57999"
height="164.03999"
fill="url(#linearGradient295062)"
fill-rule="evenodd"
id="rect19"
style="fill:url(#linearGradient295062);stroke-width:7.06973" />
<g
transform="translate(-22.74,-0.10437)"
stroke-width="7.06981"
id="g25">
<rect
x="-218.13"
y="-82.027"
width="39.220001"
height="24.316999"
rx="5.2916999"
ry="5.2916999"
fill="#e8cc4c"
fill-rule="evenodd"
id="rect21"
style="stroke-width:7.06973" />
<path
d="m -267.18,-101.62 v 63.5 m 7.9375,-63.5 v 63.5 m 7.9375,-63.5 v 63.5 m 7.9375,-63.5 v 63.5 m 7.9375,-63.5 v 63.5 m 7.9375,-63.5 v 63.5 m 7.9375,-63.5 v 63.5 m 7.9375,-63.5 v 63.5 m 7.9375,-63.5 v 63.5 m 7.9375,-63.5 v 63.5 m 7.9375,-63.5 v 63.5 m 7.9375,-63.5 v 63.5 m 7.9375,-63.5 v 63.5 m 7.9375,-63.5 v 63.5 m 7.9375,-63.5 v 63.5 m 7.9375,-63.5 v 63.5 m 7.9375,-63.5 v 63.5 m 7.9375,-63.5 v 63.5 m -134.94,-63.5 h 137.58 m -137.58,7.9375 h 137.58 m -137.58,7.9375 h 137.58 m -137.58,7.9375 h 137.58 m -137.58,7.9375 h 137.58 m -137.58,7.9375 h 137.58 m -137.58,7.9375 h 137.58 m -137.58,7.9375 h 137.58 m -137.58,7.9375 h 137.58"
clip-path="url(#clipPath294187-9)"
fill="none"
stroke="#665c0c"
stroke-width="3.74115px"
id="path23" />
</g>
<g
fill="#ffffff"
fill-rule="evenodd"
id="g37"
style="stroke-width:7.06973">
<rect
x="-240.87"
y="8.8177996"
width="151.45"
height="15.875"
rx="5.2916999"
ry="5.2916999"
id="rect27"
style="stroke-width:7.06981" />
<rect
x="-240.87"
y="-35.048"
width="40.146999"
height="15.875"
rx="5.2916999"
ry="5.2916999"
id="rect29"
style="stroke-width:7.06981" />
<rect
x="-187.03999"
y="-35.048"
width="40.146999"
height="15.875"
rx="5.2916999"
ry="5.2916999"
id="rect31"
style="stroke-width:7.06981" />
<rect
x="-133.21001"
y="-35.048"
width="40.146999"
height="15.875"
rx="5.2916999"
ry="5.2916999"
id="rect33"
style="stroke-width:7.06981" />
<rect
x="-79.375"
y="-35.048"
width="40.146999"
height="15.875"
rx="5.2916999"
ry="5.2916999"
id="rect35"
style="stroke-width:7.06981" />
</g>
</g>
<rect
x="2.7931001e-06"
y="1.1538e-07"
width="137.58"
height="63.5"
fill="#ffffff"
fill-opacity="0.46872"
style="stroke-width:0.999976;paint-order:stroke fill markers"
id="rect41" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.4 KiB

View file

@ -0,0 +1,153 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="520"
height="240"
version="1.1"
viewBox="0 0 137.58 63.5"
id="svg65"
sodipodi:docname="foreign-languages.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview67"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="2.2583333"
inkscape:cx="136.16236"
inkscape:cy="120"
inkscape:window-width="3840"
inkscape:window-height="2087"
inkscape:window-x="1920"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg65" />
<defs
id="defs29">
<clipPath
id="clipPath301434">
<rect
y="-3.9777e-7"
width="137.58"
height="63.5"
fill="#a0a0ff"
fill-opacity=".66627"
fill-rule="evenodd"
id="rect26" />
</clipPath>
</defs>
<g
id="g1521"
transform="scale(1.0000242,1)"
style="stroke-width:0.999988"
inkscape:export-filename="/home/joshua/Code/Personal/Koto/data/genres/foreign-language.png"
inkscape:export-xdpi="96.0084"
inkscape:export-ydpi="96.0084">
<rect
y="0"
width="137.58"
height="63.5"
fill="#a0a0ff"
fill-rule="evenodd"
id="rect31"
x="0"
style="stroke-width:0.999976" />
<g
clip-path="url(#clipPath301434)"
id="g53"
style="stroke-width:0.999988">
<g
id="g43"
style="stroke-width:0.999988">
<path
d="m 141.34,38.614 h -38.032 c -1.9369,0 -3.5071,1.5702 -3.5071,3.5071 v 18.824 c 0,1.9369 1.5702,3.5066 3.5071,3.5066 h 22.416 l 6.1064,11.291 v -11.291 h 9.5088 c 1.9369,0 3.5071,-1.5697 3.5071,-3.5066 v -18.824 c 0,-1.9369 -1.5702,-3.5071 -3.5071,-3.5071"
fill="#f99746"
id="path33"
style="stroke-width:0.999976" />
<path
d="m 101.58,0.73638 h 34.001 c 1.7314,0 3.1353,1.6392 3.1353,2.9966 v 11.868 c 0,2.0377 -1.4039,3.2468 -3.1353,3.2468 h -20.04 l -5.4593,7.9149 v -7.9149 h -8.5011 c -1.7314,0 -3.1353,-1.1001 -3.1353,-2.4582 v -12.051 c 0,-2.2834 1.4038,-3.6022 3.1353,-3.6022"
fill="#c0d8fb"
id="path35"
style="stroke-width:0.999976" />
<path
d="M 37.569,1.5124 H 7.089 c -1.5522,0 -2.8106,1.2578 -2.8106,2.8104 v 15.087 c 0,1.5518 1.2584,2.8104 2.8106,2.8104 h 17.965 l 4.8943,9.0492 v -9.0492 h 7.6204 c 1.5526,0 2.8104,-1.2586 2.8104,-2.8104 V 4.3228 c 0,-1.5526 -1.2578,-2.8104 -2.8104,-2.8104"
fill="#ebf3fa"
id="path37"
style="stroke-width:0.999976" />
<path
d="m 21.158,14.314 c -0.20383,-1.0087 -0.82632,-2.5543 -1.4703,-3.7031 l 0.63324,-0.22511 c 0.6761,1.1487 1.3093,2.6618 1.5239,3.6706 z m -3.8958,-3.6172 c -0.03218,0.0758 -0.10737,0.11835 -0.23609,0.11835 -0.34339,1.3205 -0.9337,2.7261 -1.6099,3.5847 -0.13947,-0.11835 -0.39707,-0.27926 -0.56888,-0.36512 0.65467,-0.81534 1.2129,-2.2 1.5455,-3.5739 z m 3.4344,-0.35352 c 0.08578,-0.39761 0.19316,-0.99868 0.28978,-1.5781 h -2.1358 v 6.2674 c 0,0.7728 -0.27903,0.91281 -1.7815,0.91281 -0.04293,-0.21505 -0.1503,-0.52602 -0.26835,-0.7403 0.26835,0.01002 0.52595,0.01002 0.72978,0.01002 h 0.42941 c 0.13939,0 0.18241,-0.04254 0.18241,-0.18256 v -6.2674 h -1.728 c -0.32188,0.78362 -0.69753,1.4922 -1.1054,2.0283 -0.12881,-0.12841 -0.37564,-0.34346 -0.54745,-0.44016 0.77272,-0.9979 1.406,-2.6827 1.7709,-4.3676 l 0.89084,0.23594 c -0.02143,0.075032 -0.09655,0.11835 -0.23617,0.10753 -0.13947,0.59023 -0.31121,1.1913 -0.51512,1.7599 h 4.7438 l 0.37557,0.010777 c 0,0.18256 -0.2576,1.5881 -0.48301,2.3609 z m -7.3517,-0.2576 c -0.35422,0.62195 -0.72986,1.1913 -1.1163,1.674 -0.09662,-0.15007 -0.32188,-0.42933 -0.46151,-0.5794 1.0089,-1.2021 1.9748,-3.2412 2.5866,-5.227 l 0.89084,0.26843 c -0.04301,0.10753 -0.13964,0.12841 -0.2576,0.11835 -0.28986,0.85866 -0.63324,1.7274 -1.0196,2.5435 l 0.31128,0.085872 c -0.02158,0.064217 -0.0752,0.11758 -0.21474,0.12841 v 6.826 h -0.71896 v -5.8381"
fill="#2b478b"
opacity="0.54345"
id="path39"
style="stroke-width:0.999976" />
<path
d="m 33.617,10.751 v 0.69776 h -2.586 v 3.5522 c 0,0.82617 -0.32258,0.95535 -2.2217,0.93369 -0.04332,-0.19339 -0.1609,-0.50437 -0.26843,-0.71942 0.5152,0.01078 0.98707,0.02166 1.288,0.02166 0.36512,0 0.47187,0 0.47187,-0.22511 v -3.563 h -2.6294 v -0.69776 h 2.6294 v -1.8248 l 0.35429,0.021655 c 0.55852,-0.47265 1.1487,-1.148 1.5881,-1.749 h -4.1316 v -0.67688 h 4.6793 l 0.13924,-0.042537 0.56934,0.35429 c -0.03248,0.053389 -0.10753,0.085872 -0.17173,0.10753 -0.55852,0.79368 -1.4814,1.8132 -2.2967,2.4785 v 1.3313 z M 25.4284,8.9053 c -0.22534,0.9979 -0.47218,2.0066 -0.6976,2.887 0.45068,0.24677 0.91227,0.54691 1.3629,0.84783 0.42964,-0.99868 0.74062,-2.2433 0.89069,-3.7348 z m 1.932,-0.70859 0.40767,0.10753 -0.06422,0.15007 c -0.15084,1.8991 -0.49353,3.4238 -1.0304,4.6151 0.52602,0.40767 0.97624,0.80451 1.2663,1.1696 l -0.44016,0.59023 c -0.26842,-0.34346 -0.67609,-0.73025 -1.148,-1.1163 -0.54761,0.9979 -1.2666,1.7281 -2.1253,2.2433 -0.09662,-0.18256 -0.30053,-0.44016 -0.45068,-0.59023 0.8049,-0.42933 1.481,-1.1271 2.0176,-2.0933 -0.39707,-0.30014 -0.81565,-0.5794 -1.2235,-0.82617 l -0.12879,0.47188 -0.61174,-0.32181 c 0.26828,-0.94452 0.60099,-2.3076 0.91227,-3.6915 h -1.0733 V 8.22824 h 1.2235 c 0.17173,-0.80451 0.32188,-1.5773 0.45068,-2.2642 l 0.88009,0.10753 c -0.01078,0.075032 -0.07503,0.11758 -0.21459,0.12841 -0.11804,0.61189 -0.26827,1.3096 -0.42925,2.0283 h 1.6525 l 0.12919,-0.031709"
fill="#2b478b"
opacity="0.54345"
id="path41"
style="stroke-width:0.999976" />
</g>
<text
x="107.10843"
y="11.536377"
fill="#333333"
font-family="sans-serif"
font-size="6.7091px"
letter-spacing="0px"
opacity="0.53934"
stroke-width="0.264577px"
word-spacing="0px"
style="line-height:4.19317px"
xml:space="preserve"
id="text47"><tspan
x="107.10843"
y="11.536377"
fill="#333333"
font-family="'Noto Sans'"
font-size="6.7091px"
font-stretch="condensed"
font-weight="bold"
stroke-width="0.264577px"
id="tspan45">Bonjour</tspan></text>
<text
x="111.31922"
y="55.259583"
fill="#5a5a5a"
font-family="sans-serif"
font-size="9.9404px"
letter-spacing="0px"
opacity="0.48424"
stroke-width="0.264577px"
word-spacing="0px"
style="line-height:6.21273px"
xml:space="preserve"
id="text51"><tspan
x="111.31922"
y="55.259583"
fill="#5a5a5a"
font-family="'Noto Sans'"
font-size="9.9404px"
font-stretch="condensed"
font-weight="bold"
stroke-width="0.264577px"
id="tspan49">Hello</tspan></text>
</g>
<rect
y="0"
width="137.58"
height="63.5"
fill="#a0a0ff"
fill-opacity="0.66627"
fill-rule="evenodd"
id="rect55"
x="0"
style="stroke-width:0.999976" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.5 KiB

View file

@ -0,0 +1,572 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="520"
height="240"
version="1.1"
viewBox="0 0 137.58 63.5"
id="svg296"
sodipodi:docname="mystery-and-thriller.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview298"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="2.2583333"
inkscape:cx="136.16236"
inkscape:cy="120"
inkscape:window-width="3840"
inkscape:window-height="2087"
inkscape:window-x="1920"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg296" />
<defs
id="defs72">
<clipPath
id="clipPath302672">
<rect
x="15.037"
y="6.529"
width="114.27"
height="52.739"
fill="#333"
fill-rule="evenodd"
opacity=".56765"
id="rect69" />
</clipPath>
</defs>
<g
id="g1377"
transform="scale(1.000023,0.99999772)"
style="stroke-width:0.999989"
inkscape:export-filename="/home/joshua/Code/Personal/Koto/data/genres/mystery-and-thriller.png"
inkscape:export-xdpi="96.0084"
inkscape:export-ydpi="96.0084">
<rect
x="0.0054583"
y="0.065999985"
width="137.58"
height="63.5"
fill="#333333"
fill-rule="evenodd"
id="rect74"
style="stroke-width:0.999978" />
<g
transform="matrix(1.204,0,0,1.204,-18.1,-7.7951)"
clip-path="url(#clipPath302672)"
stroke-width="0.830531"
id="g284">
<g
stroke-width="0.830531"
id="g266">
<path
d="m 130.09,41.568 c 0.14098,0.10843 2.3979,1.8472 2.5041,2.1724 0.10831,0.32528 -0.8044,3.3497 -1.3399,4.2255 -0.0152,0.02595 -0.0325,0.05201 -0.0476,0.07379 -1.7019,2.6623 -4.438,4.8715 -7.8071,6.3197 1.6563,-1.8731 2.5127,-4.9604 2.5127,-4.9604 l -1.0428,-3.6921 2.387,-0.55303 0.49429,-2.6926 c -0.53762,-1.4808 -1.7778,-2.5084 -1.7778,-2.5084 -0.26226,-0.35552 -0.55499,-0.70895 -0.86939,-1.0341 0,0 6.0706,2.4368 7.9914,3.6401 h 0.002 c -0.0108,0.15615 -0.0282,0.30791 -0.0476,0.46174 l -2.9594,-1.4525"
fill="#2c2c2c"
id="path76"
style="stroke-width:0.830522" />
<path
d="m 130.09,41.568 2.9594,1.4525 c -0.21246,1.7584 -0.83476,3.4257 -1.7952,4.9454 0.53554,-0.87588 1.4482,-3.9003 1.3399,-4.2255 -0.10623,-0.32517 -2.3632,-2.0639 -2.5041,-2.1724"
fill="#878787"
id="path78"
style="stroke-width:0.830522" />
<path
d="m 128.21,21.038 c 1.4786,0.45097 3.2932,1.7345 3.2196,3.1805 0,0 -0.35992,-0.73281 -1.3161,-1.7345 -0.89534,-0.93878 -1.7951,-1.3918 -1.9035,-1.446"
fill="#1e1e1e"
id="path80"
style="stroke-width:0.830522" />
<path
d="m 131.43,24.219 c 0.0737,-1.446 -1.741,-2.7296 -3.2196,-3.1805 -0.004,-0.0021 -0.007,-0.0021 -0.0109,-0.0043 0,0 0.004,0.0022 0.0109,0.0043 0.10842,0.05421 1.0082,0.50727 1.9035,1.446 0.95615,1.0017 1.3161,1.7345 1.3161,1.7345 z m -2.0163,2.8184 c -0.16044,0.07588 -5.3876,2.5193 -15.395,2.5193 v -4.2298 c 3.3345,0 6.4196,-0.48781 8.3427,-1.2857 1.8385,-0.76305 2.7209,-1.4655 2.736,-1.7344 0.0152,-0.27107 -1.2314,-0.48352 -1.2314,-0.48352 l -0.0196,0.0022 c -0.0672,-0.53333 -0.15824,-1.1078 -0.25798,-1.704 -0.013,-0.07159 -0.0259,-0.14527 -0.039,-0.21906 l 0.28185,-0.0065 c 8.219,0.54202 9.0712,4.1302 7.367,6.2375 -1.006,1.2445 -2.6992,2.4888 -6.3437,3.2997 -0.0888,0.01946 -0.1799,0.03904 -0.27096,0.0585 -0.50089,0.10634 -1.0363,0.20388 -1.6109,0.29273 -2.3003,0.35552 -5.2206,0.56588 -8.9541,0.56588 v -0.38158 c 9.0993,0 13.108,-1.4049 15.395,-2.9312"
fill="#2c2c2c"
id="path82"
style="stroke-width:0.830522" />
<path
d="m 126.82,44.722 h 0.002 c 0.38586,-1.3984 0.60052,-2.3502 0.60052,-2.3502 0,0 -0.5616,-1.2076 -1.4591,-2.4196 0,0 1.2402,1.0276 1.7778,2.5084 l -0.4943,2.6926 -2.387,0.55303 1.0428,3.6921 c 0,0 -0.85641,3.0873 -2.5127,4.9604 -0.27964,0.12141 -0.56368,0.2363 -0.85202,0.34472 1.1577,-1.3072 2.1874,-3.6185 2.9983,-5.8883 -0.0152,-0.03695 -1.3246,-3.2955 -1.3246,-3.2955 l 0.76745,-0.23423 1.8407,-0.56357"
fill="#1e1e1e"
id="path84"
style="stroke-width:0.830522" />
<path
d="m 125.1,48.82 c -0.0411,0.02815 -0.68508,0.43788 -2.1875,-0.34254 -1.5935,-0.82826 -1.5935,-4.49 -1.5935,-4.49 0.0305,3.1805 -0.78911,8.8651 -1.8428,11.642 -0.0607,0.01516 -0.1236,0.02815 -0.18639,0.04114 0.54423,-2.3284 1.4027,-9.1577 1.6326,-14.329 0.078,-1.728 0.0846,-3.2695 -0.0152,-4.3968 0.45306,-0.63087 0.77394,-1.1707 0.86059,-1.496 1.2272,1.6737 3.3345,3.4689 3.3345,3.4689 0.31439,0.32517 0.60712,0.6786 0.86939,1.0341 0.89753,1.2119 1.4591,2.4196 1.4591,2.4196 0,0 -0.21465,0.95175 -0.60052,2.3502 h -0.002 l -1.8407,0.56357 -2.9745,-4.2991 c 0.0824,0.14526 1.4113,2.4822 2.207,4.5333 0,0 1.3095,3.2585 1.3246,3.2955 -0.81089,2.2698 -1.8406,4.5811 -2.9983,5.8883 l -0.002,0.0021 c -0.17561,0.06733 -0.35563,0.13244 -0.53553,0.1952 0.9865,-1.3896 2.1528,-3.3908 3.0916,-6.0813"
fill="#2c2c2c"
id="path86"
style="stroke-width:0.830522" />
<g
fill="#1e1e1e"
id="g94"
style="stroke-width:0.830522">
<path
d="m 114.02,29.557 c 10.008,0 15.235,-2.4434 15.395,-2.5193 -2.2873,1.5263 -6.296,2.9312 -15.395,2.9312 v -0.41193"
id="path88"
style="stroke-width:0.830531" />
<path
d="m 123.87,21.823 c 0,0 1.2466,0.21245 1.2314,0.48352 -0.0152,0.26887 -0.89753,0.97133 -2.736,1.7344 -1.9231,0.79791 -5.0082,1.2857 -8.3427,1.2857 v -0.40544 c 7.625,0 9.9123,-2.3307 9.9123,-2.3307 -0.0217,-0.24489 -0.0498,-0.50078 -0.0846,-0.76525 l 0.0196,-0.0022"
id="path90"
style="stroke-width:0.830531" />
<path
d="m 122.01,40.987 2.9745,4.2991 -0.76745,0.23423 c -0.79571,-2.0511 -2.1246,-4.3881 -2.207,-4.5333"
id="path92"
style="stroke-width:0.830531" />
</g>
<path
d="m 124.83,29.633 c -0.16693,1.1642 -0.71764,1.9599 -2.0358,2.244 0,0 1.0082,-0.78054 1.2423,-1.0776 0.54643,-0.69377 0.54643,-1.3074 0.54643,-1.3074 0.0911,-0.01946 0.1821,-0.03904 0.27095,-0.0585 -0.006,0.06719 -0.0152,0.13449 -0.0239,0.19948"
fill="#878787"
id="path96"
style="stroke-width:0.830522" />
<path
d="m 122.91,48.477 c 1.5025,0.78042 2.1464,0.37069 2.1875,0.34254 -0.93877,2.6905 -2.1051,4.6917 -3.0916,6.0813 -0.81309,0.28626 -1.6607,0.53125 -2.5323,0.72854 1.0537,-2.7773 1.8733,-8.4619 1.8428,-11.642 0,0 0,3.6617 1.5935,4.49"
fill="#2c2c2c"
id="path98"
style="stroke-width:0.830522" />
<path
d="m 124.59,29.492 c 0,0 0,0.61361 -0.54643,1.3074 -0.23411,0.29702 -1.2423,1.0776 -1.2423,1.0776 -0.0824,0.01726 -0.16692,0.03464 -0.25577,0.04761 0.15823,-0.75876 0.29481,-1.4374 0.36849,-1.8189 0.0412,-0.20388 0.0651,-0.32088 0.0651,-0.32088 0.57457,-0.08885 1.11,-0.18639 1.6109,-0.29273"
fill="#1e1e1e"
id="path100"
style="stroke-width:0.830522" />
<path
d="m 115.13,21.015 c 4.774,-0.16264 7.5469,-1.342 8.3167,-1.7193 0.0237,0.13229 0.0476,0.26226 0.0715,0.39235 -0.85201,0.51827 -2.0987,0.89117 -3.6032,1.1838 0,0 1.5848,0.34254 3.6791,-0.75007 0.0997,0.59624 0.19079,1.1707 0.25798,1.704 0.0348,0.26446 0.0629,0.52036 0.0846,0.76525 0,0 -2.2873,2.3307 -9.9123,2.3307 v -3.8873 c 0.3795,0 0.74799,-0.0065 1.1057,-0.01946"
fill="#4d4d4d"
id="path102"
style="stroke-width:0.830522" />
<path
d="m 123.55,19.902 c 0.0131,0.07379 0.0261,0.14747 0.039,0.21906 -2.0943,1.0926 -3.6791,0.75007 -3.6791,0.75007 1.5046,-0.29262 2.7512,-0.66551 3.6032,-1.1838 0.013,0.07159 0.0239,0.14318 0.0368,0.21465"
fill="#2c2c2c"
id="path104"
style="stroke-width:0.830522" />
<path
d="m 123.35,18.777 c 0.0325,0.17341 0.0651,0.34683 0.0955,0.51816 -0.76977,0.3773 -3.5427,1.5567 -8.3167,1.7193 5.0733,-0.51827 6.9551,-1.548 8.2212,-2.2375"
fill="#1e1e1e"
id="path106"
style="stroke-width:0.830522" />
<path
d="m 122.3,12.542 c 0.15604,1.7301 0.63516,4.0499 1.0428,6.2353 -1.2661,0.68949 -3.148,1.7192 -8.2212,2.2375 -0.35772,0.01297 -0.72621,0.01946 -1.1057,0.01946 v -8.5594 c 3.432,0 3.677,-2.0185 3.677,-2.0185 -0.71973,0.51388 -1.4786,0.96484 -3.677,0.96484 v -0.26887 c 0.45538,0.02386 1.1405,0.0065 1.9275,-0.22554 0.89973,-0.26226 1.548,-0.68926 1.9294,-0.98639 0.63967,-0.023872 1.4375,0.0044 2.9897,1.1209 0.68301,0.49221 1.1405,1.0472 1.4375,1.4808 z m -2.1139,-1.2791 c 0.0629,0.0563 1.0602,0.94955 1.2987,1.8818 0.245,0.96473 0.9192,4.284 0.9192,4.284 l -0.40325,-4.529 c 0,0 -0.33814,-0.85201 -1.8146,-1.6368"
fill="#2c2c2c"
id="path108"
style="stroke-width:0.830522" />
<path
d="m 122.97,29.784 c 0,0 -0.0239,0.117 -0.0651,0.32088 0,0 -0.53114,1.3138 -0.92789,2.5669 -0.4943,0.22554 -0.99728,0.24292 -2.4,0.26887 -1.8169,0.03255 -2.3328,0.39235 -3.0504,0.41413 -0.71764,0.02155 -1.5025,-0.83047 -2.5106,-0.83047 v -2.1745 c 3.7334,0 6.6538,-0.21037 8.9541,-0.56588"
fill="#1e1e1e"
id="path110"
style="stroke-width:0.830522" />
<path
d="m 122.91,30.105 c -0.0737,0.38158 -0.21026,1.0602 -0.36849,1.8189 -0.0326,0.16056 -0.0672,0.32517 -0.10195,0.49001 -0.16692,0.10843 -0.30999,0.19299 -0.45745,0.25798 0.39675,-1.2532 0.92789,-2.5669 0.92789,-2.5669"
fill="#878787"
id="path112"
style="stroke-width:0.830522" />
<path
d="m 122.01,12.9 0.40325,4.529 c 0,0 -0.6742,-3.3193 -0.9192,-4.284 -0.23852,-0.93229 -1.2358,-1.8255 -1.2987,-1.8818 1.4765,0.78482 1.8146,1.6368 1.8146,1.6368"
fill="#4d4d4d"
id="path114"
style="stroke-width:0.830522" />
<path
d="m 121.98,32.672 c 0.14746,-0.06499 0.29053,-0.14955 0.45745,-0.25798 -0.24928,1.1946 -0.52035,2.4629 -0.67431,3.0353 -0.0866,0.32528 -0.40753,0.8651 -0.86058,1.496 -0.30791,0.42919 -0.67651,0.89974 -1.0732,1.3745 -0.74799,0.89117 -1.5979,1.7974 -2.3459,2.4607 0.12361,-0.12349 1.2769,-1.2834 2.2764,-2.851 1.0581,-1.6563 1.5935,-2.5322 1.8624,-3.9241 0.0737,-0.38587 0.20597,-0.8541 0.35772,-1.3333"
fill="#fbfbfb"
id="path116"
style="stroke-width:0.830522" />
<path
d="m 116.18,34.357 c 0.69389,0.17341 1.5134,1.3268 1.6283,2.2375 0.013,0.16913 0.013,0.27536 0.013,0.27536 0.004,-0.08897 0,-0.18002 -0.013,-0.27536 -0.0217,-0.33826 -0.0803,-0.92569 -0.24071,-1.3853 -0.20377,-0.57886 -1.11,-0.79792 -1.3876,-0.85213 z m -0.63296,7.5643 c -0.60492,0.14306 -1.2336,0.16693 -1.5242,0.16693 v -3.1415 c 0.81089,0 1.7756,-0.58755 1.8363,-0.62438 -0.0454,0.0044 -0.60481,0.07159 -1.8363,0.07159 v -0.88236 c 1.6305,0 3.1848,-0.22334 3.1848,-0.22334 -0.83684,-0.01517 -2.168,-0.35992 -2.3782,-0.34254 -0.20817,0.01726 -0.80661,0.41622 -0.80661,0.41622 v -1.7474 c 0.6808,0 0.98871,-0.27107 1.1275,-0.52256 0.8541,-0.1821 0.94736,-0.75019 0.94736,-0.75019 -0.14735,0.20168 -0.33595,0.34903 -0.83464,0.32957 0,0 -0.44877,0.62879 -1.2402,0.62879 v -2.7752 c 1.0082,0 1.793,0.85201 2.5106,0.83047 0.71764,-0.02178 1.2336,-0.38158 3.0504,-0.41413 1.4027,-0.02595 1.9057,-0.04333 2.4,-0.26887 -0.15175,0.47924 -0.28405,0.94747 -0.35772,1.3333 -0.26887,1.3919 -0.80429,2.2678 -1.8624,3.9241 -0.99948,1.5676 -2.1528,2.7275 -2.2764,2.851 -0.006,0.0044 -0.0109,0.0088 -0.0109,0.0088 -0.4812,0.42699 -0.917,0.75227 -1.2553,0.90831 -0.20596,0.09545 -0.43788,0.16913 -0.67419,0.22334"
fill="#878787"
id="path118"
style="stroke-width:0.830522" />
<path
d="m 120.9,36.945 c 0.0997,1.1273 0.0931,2.6688 0.0152,4.3968 -0.0846,0.56148 -0.19079,1.097 -0.30362,1.5869 -0.37498,1.6305 -0.82375,2.7774 -0.82375,2.7774 -0.84344,-1.173 -2.0489,-2.2028 -2.9616,-2.8922 -0.34034,-0.25589 -0.63955,-0.46614 -0.8629,-0.6157 1.6738,-0.52685 2.6189,-1.8364 3.8635,-3.8787 0.39664,-0.47472 0.76525,-0.94526 1.0732,-1.3745"
fill="#fbfbfb"
id="path120"
style="stroke-width:0.830522" />
<path
d="m 120.62,42.929 c 0.11283,-0.48989 0.21906,-1.0254 0.30362,-1.5869 -0.22983,5.1708 -1.0883,12 -1.6326,14.329 -0.34903,0.07597 -0.70246,0.14525 -1.0581,0.20593 1.1123,-3.0938 2.3502,-12.648 2.387,-12.948"
fill="#878787"
id="path122"
style="stroke-width:0.830522" />
<path
d="m 119.79,45.706 c 0,0 0.44877,-1.1469 0.82374,-2.7774 -0.0368,0.29922 -1.2747,9.8538 -2.387,12.948 -0.36849,0.0629 -0.7393,0.11933 -1.1165,0.16485 0,0 -0.49001,-4.9084 -0.64605,-6.7621 -0.15835,-1.8537 -1.0407,-4.2321 -1.0407,-4.2321 l 0.81089,-1.524 0.22983,-0.27315 c 0,0 2.0401,1.2531 3.3258,2.4564"
fill="#fbfbfb"
id="path124"
style="stroke-width:0.830522" />
<path
d="m 117.48,40.78 c 0.74799,-0.66331 1.5979,-1.5695 2.3459,-2.4607 -1.2446,2.0423 -2.1898,3.3519 -3.8635,3.8787 -0.24709,-0.16704 -0.40104,-0.26458 -0.42271,-0.27756 0.23632,-0.05421 0.46823,-0.12789 0.6742,-0.22334 0.33825,-0.15604 0.77405,-0.48132 1.2552,-0.90831 0,0 0.004,-0.0044 0.0109,-0.0088"
fill="#2c2c2c"
id="path126"
style="stroke-width:0.830522" />
<path
d="m 117.56,35.209 c 0.16043,0.45966 0.21905,1.0471 0.24071,1.3853 -0.11491,-0.91063 -0.93437,-2.0641 -1.6283,-2.2375 0.27755,0.05421 1.1838,0.27327 1.3876,0.85213"
fill="#2c2c2c"
id="path128"
style="stroke-width:0.830522" />
<g
fill="#1e1e1e"
id="g136"
style="stroke-width:0.830522">
<path
d="m 119.79,45.706 c -1.2857,-1.2034 -3.3258,-2.4564 -3.3258,-2.4564 l 0.36421,-0.4358 c 0.91271,0.68937 2.1182,1.7192 2.9616,2.8922"
id="path130"
style="stroke-width:0.830531" />
<path
d="m 117.7,10.456 c 0,0 -0.24501,2.0185 -3.677,2.0185 v -1.0537 c 2.1984,0 2.9573,-0.45097 3.677,-0.96484"
id="path132"
style="stroke-width:0.830531" />
<path
d="m 117.11,56.041 c -0.19079,0.02388 -0.38158,0.04542 -0.57457,0.06498 0,0 -0.24489,-5.2576 -0.54411,-7.3496 -0.29922,-2.0943 -0.9865,-3.768 -0.9865,-3.768 l 1.2293,-1.4656 -0.81089,1.524 c 0,0 0.88237,2.3785 1.0407,4.2321 0.15604,1.8537 0.64605,6.7621 0.64605,6.7621"
id="path134"
style="stroke-width:0.830531" />
</g>
<g
fill="#2c2c2c"
id="g144"
style="stroke-width:0.830522">
<path
d="m 116.83,42.814 -0.36421,0.4358 -0.22983,0.27315 -1.2293,1.4656 h -0.98651 v -2.528 c 0.75459,0 1.392,-0.08885 1.947,-0.26226 0.22334,0.14955 0.52256,0.3598 0.8629,0.6157"
id="path138"
style="stroke-width:0.830531" />
<path
d="m 116.54,56.106 c -0.14086,0.0152 -0.28184,0.02815 -0.42271,0.03696 l -0.002,-0.03696 c 0.12141,-2.1768 -0.34034,-6.9616 -0.34034,-6.9616 0,0 -0.0477,5.8733 -0.87379,7.0721 -0.29041,0.01085 -0.58314,0.01516 -0.87807,0.01516 v -11.243 h 0.9865 c 0,0 0.68728,1.6737 0.9865,3.768 0.29922,2.0921 0.54411,7.3496 0.54411,7.3496"
id="path140"
style="stroke-width:0.830531" />
<path
d="m 117.21,37.288 c 0,0 -1.5544,0.22334 -3.1848,0.22334 v -0.14967 c 0,0 0.59844,-0.39896 0.80661,-0.41622 0.21025,-0.01738 1.5414,0.32737 2.3782,0.34254"
id="path142"
style="stroke-width:0.830531" />
</g>
<path
d="m 115.77,49.145 c 0,0 0.46174,4.7848 0.34034,6.9616 l 0.002,0.03696 c -0.40104,0.03453 -0.80649,0.0606 -1.2163,0.07358 0.82606,-1.1989 0.87379,-7.0721 0.87379,-7.0721"
fill="#878787"
id="path146"
style="stroke-width:0.830522" />
<path
d="m 116.1,34.341 c 0,0 -0.0933,0.56809 -0.94736,0.75019 0.11921,-0.21906 0.11272,-0.42062 0.11272,-0.42062 0.49869,0.01946 0.68729,-0.12789 0.83464,-0.32957"
fill="#1e1e1e"
id="path148"
style="stroke-width:0.830522" />
<path
d="m 115.54,41.921 c 0.0217,0.01297 0.17562,0.11051 0.42271,0.27756 -0.555,0.17341 -1.1924,0.26226 -1.947,0.26226 v -0.37289 c 0.29065,0 0.91932,-0.02386 1.5242,-0.16693"
fill="#1e1e1e"
id="path150"
style="stroke-width:0.830522" />
<path
d="m 114.02,38.394 c 1.2315,0 1.7909,-0.06719 1.8363,-0.07159 -0.0607,0.03684 -1.0254,0.62438 -1.8363,0.62438 v -0.5528"
fill="#878787"
id="path152"
style="stroke-width:0.830522" />
<path
d="m 115.15,35.092 c -0.13877,0.25149 -0.44668,0.52256 -1.1275,0.52256 v -0.31439 c 0.79143,0 1.2402,-0.62879 1.2402,-0.62879 0,0 0.007,0.20156 -0.11272,0.42062"
fill="#1e1e1e"
id="path154"
style="stroke-width:0.830522" />
<path
d="m 97.954,41.568 c -0.14086,0.10843 -2.3978,1.8472 -2.504,2.1724 -0.10838,0.32528 0.80437,3.3497 1.3398,4.2255 0.01517,0.02595 0.03254,0.05201 0.04773,0.07379 1.7019,2.6623 4.438,4.8715 7.8071,6.3197 -1.6564,-1.8731 -2.5128,-4.9604 -2.5128,-4.9604 l 1.0429,-3.6921 -2.387,-0.55303 -0.49429,-2.6926 c 0.53762,-1.4808 1.7777,-2.5084 1.7777,-2.5084 0.26239,-0.35552 0.555,-0.70895 0.86939,-1.0341 0,0 -6.0705,2.4368 -7.9914,3.6401 h -0.002 c 0.01087,0.15615 0.02814,0.30791 0.04769,0.46174 l 2.9593,-1.4525"
fill="#2c2c2c"
id="path156"
style="stroke-width:0.830522" />
<path
d="m 97.954,41.568 -2.9593,1.4525 c 0.2125,1.7584 0.83469,3.4257 1.7951,4.9454 -0.53548,-0.87588 -1.4482,-3.9003 -1.3398,-4.2255 0.10625,-0.32517 2.3632,-2.0639 2.504,-2.1724"
fill="#878787"
id="path158"
style="stroke-width:0.830522" />
<path
d="m 99.828,21.038 c -1.4786,0.45097 -3.2933,1.7345 -3.2196,3.1805 0,0 0.35992,-0.73281 1.316,-1.7345 0.89545,-0.93878 1.7952,-1.3918 1.9036,-1.446"
fill="#1e1e1e"
id="path160"
style="stroke-width:0.830522" />
<path
d="m 96.608,24.219 c -0.07372,-1.446 1.741,-2.7296 3.2196,-3.1805 0.0043,-0.0021 0.0065,-0.0021 0.0109,-0.0043 0,0 -0.0044,0.0022 -0.0109,0.0043 -0.10843,0.05421 -1.0082,0.50727 -1.9036,1.446 -0.95604,1.0017 -1.316,1.7345 -1.316,1.7345 z m 7.8419,-4.0975 c 0.013,-0.07159 0.0259,-0.14527 0.0389,-0.21906 l -0.28184,-0.0065 c -8.219,0.54202 -9.071,4.1302 -7.3669,6.2375 1.006,1.2445 2.6992,2.4888 6.3436,3.2997 0.089,0.01946 0.18002,0.03904 0.27107,0.0585 0.50078,0.10634 1.0363,0.20388 1.6109,0.29273 2.3003,0.35552 5.2206,0.56588 8.954,0.56588 v -0.38158 c -9.0992,0 -13.108,-1.4049 -15.395,-2.9312 0.16032,0.07588 5.3875,2.5193 15.395,2.5193 v -4.2298 c -3.3345,0 -6.4196,-0.48781 -8.3427,-1.2857 -1.8384,-0.76305 -2.7209,-1.4655 -2.7361,-1.7344 -0.0152,-0.27107 1.2315,-0.48352 1.2315,-0.48352 l 0.0195,0.0022 c 0.0672,-0.53333 0.15823,-1.1078 0.25809,-1.704"
fill="#2c2c2c"
id="path162"
style="stroke-width:0.830522" />
<path
d="m 101.22,44.722 h -0.002 c -0.38587,-1.3984 -0.60052,-2.3502 -0.60052,-2.3502 0,0 0.56148,-1.2076 1.459,-2.4196 0,0 -1.2401,1.0276 -1.7777,2.5084 l 0.49429,2.6926 2.387,0.55303 -1.0429,3.6921 c 0,0 0.85641,3.0873 2.5128,4.9604 0.27964,0.12141 0.56368,0.2363 0.85201,0.34472 -1.1577,-1.3072 -2.1876,-3.6185 -2.9984,-5.8883 0.0152,-0.03695 1.3246,-3.2955 1.3246,-3.2955 l -0.76745,-0.23423 -1.8406,-0.56357"
fill="#1e1e1e"
id="path164"
style="stroke-width:0.830522" />
<path
d="m 106.03,40.987 c -0.0824,0.14526 -1.4114,2.4822 -2.2071,4.5333 0,0 -1.3095,3.2585 -1.3246,3.2955 0.81089,2.2698 1.8407,4.5811 2.9984,5.8883 l 0.002,0.0021 c 0.17561,0.06733 0.35551,0.13244 0.53542,0.1952 -0.98639,-1.3896 -2.1528,-3.3908 -3.0916,-6.0813 0.0412,0.02815 0.68509,0.43788 2.1876,-0.34254 1.5935,-0.82826 1.5935,-4.49 1.5935,-4.49 -0.0303,3.1805 0.78911,8.8651 1.8428,11.642 0.0607,0.01516 0.1236,0.02815 0.1865,0.04113 -0.54422,-2.3284 -1.4027,-9.1577 -1.6326,-14.329 -0.0781,-1.728 -0.0846,-3.2695 0.0152,-4.3968 -0.45317,-0.63087 -0.77405,-1.1707 -0.8607,-1.496 -1.2271,1.6737 -3.3345,3.4689 -3.3345,3.4689 -0.3144,0.32517 -0.60701,0.6786 -0.86939,1.0341 -0.89754,1.2119 -1.459,2.4196 -1.459,2.4196 0,0 0.21465,0.95175 0.60052,2.3502 h 0.002 l 1.8406,0.56357 2.9746,-4.2991"
fill="#2c2c2c"
id="path166"
style="stroke-width:0.830522" />
<g
fill="#1e1e1e"
id="g174"
style="stroke-width:0.830522">
<path
d="m 114.02,29.557 c -10.008,0 -15.235,-2.4434 -15.395,-2.5193 2.2873,1.5263 6.296,2.9312 15.395,2.9312 v -0.41193"
id="path168"
style="stroke-width:0.830531" />
<path
d="m 104.17,21.823 c 0,0 -1.2467,0.21245 -1.2315,0.48352 0.0152,0.26887 0.89766,0.97133 2.7361,1.7344 1.9231,0.79791 5.0082,1.2857 8.3427,1.2857 v -0.40544 c -7.625,0 -9.9123,-2.3307 -9.9123,-2.3307 0.0218,-0.24489 0.0499,-0.50078 0.0846,-0.76525 l -0.0195,-0.0022"
id="path170"
style="stroke-width:0.830531" />
<path
d="m 106.03,40.987 -2.9746,4.2991 0.76745,0.23423 c 0.79571,-2.0511 2.1248,-4.3881 2.2071,-4.5333"
id="path172"
style="stroke-width:0.830531" />
</g>
<path
d="m 103.21,29.633 c 0.16681,1.1642 0.71752,1.9599 2.0357,2.244 0,0 -1.008,-0.78054 -1.2423,-1.0776 -0.5463,-0.69377 -0.5463,-1.3074 -0.5463,-1.3074 -0.091,-0.01946 -0.1821,-0.03904 -0.27107,-0.0585 0.007,0.06719 0.0152,0.13449 0.024,0.19948"
fill="#878787"
id="path176"
style="stroke-width:0.830522" />
<path
d="m 105.13,48.477 c -1.5025,0.78042 -2.1463,0.37069 -2.1876,0.34254 0.93879,2.6905 2.1052,4.6917 3.0916,6.0813 0.81309,0.28626 1.6608,0.53125 2.5323,0.72854 -1.0537,-2.7773 -1.8732,-8.4619 -1.8428,-11.642 0,0 0,3.6617 -1.5935,4.49"
fill="#2c2c2c"
id="path178"
style="stroke-width:0.830522" />
<path
d="m 103.45,29.492 c 0,0 0,0.61361 0.5463,1.3074 0.23424,0.29702 1.2423,1.0776 1.2423,1.0776 0.0825,0.01726 0.16693,0.03464 0.25589,0.04761 -0.15824,-0.75876 -0.29493,-1.4374 -0.36861,-1.8189 -0.0411,-0.20388 -0.065,-0.32088 -0.065,-0.32088 -0.57458,-0.08885 -1.1101,-0.18639 -1.6109,-0.29273"
fill="#1e1e1e"
id="path180"
style="stroke-width:0.830522" />
<path
d="m 112.91,21.015 c -4.774,-0.16264 -7.5469,-1.342 -8.3166,-1.7193 -0.0239,0.13229 -0.0477,0.26226 -0.0716,0.39235 0.85213,0.51827 2.0987,0.89117 3.6034,1.1838 0,0 -1.5848,0.34254 -3.6791,-0.75007 -0.0999,0.59624 -0.19091,1.1707 -0.25809,1.704 -0.0346,0.26446 -0.0628,0.52036 -0.0846,0.76525 0,0 2.2873,2.3307 9.9123,2.3307 v -3.8873 c -0.37938,0 -0.74799,-0.0065 -1.1057,-0.01946"
fill="#4d4d4d"
id="path182"
style="stroke-width:0.830522" />
<path
d="m 104.49,19.902 c -0.013,0.07379 -0.0259,0.14747 -0.0389,0.21906 2.0943,1.0926 3.6791,0.75007 3.6791,0.75007 -1.5047,-0.29262 -2.7512,-0.66551 -3.6034,-1.1838 -0.013,0.07159 -0.0239,0.14318 -0.0368,0.21465"
fill="#2c2c2c"
id="path184"
style="stroke-width:0.830522" />
<path
d="m 104.69,18.777 c -0.0324,0.17341 -0.065,0.34683 -0.0953,0.51816 0.76965,0.3773 3.5425,1.5567 8.3166,1.7193 -5.0732,-0.51827 -6.955,-1.548 -8.2212,-2.2375"
fill="#1e1e1e"
id="path186"
style="stroke-width:0.830522" />
<path
d="m 105.74,12.542 c -0.15616,1.7301 -0.63528,4.0499 -1.0429,6.2353 1.2663,0.68949 3.1481,1.7192 8.2212,2.2375 0.35772,0.01297 0.72633,0.01946 1.1057,0.01946 v -8.5594 c -3.432,0 -3.677,-2.0185 -3.677,-2.0185 0.71984,0.51388 1.4786,0.96484 3.677,0.96484 v -0.26887 c -0.45526,0.02386 -1.1404,0.0065 -1.9274,-0.22554 -0.89974,-0.26226 -1.548,-0.68926 -1.9296,-0.98639 -0.63956,-0.023872 -1.4375,0.0044 -2.9898,1.1209 -0.68288,0.49221 -1.1403,1.0472 -1.4374,1.4808 z m 2.1138,-1.2791 c -0.0628,0.0563 -1.0602,0.94955 -1.2986,1.8818 -0.245,0.96473 -0.91931,4.284 -0.91931,4.284 l 0.40324,-4.529 c 0,0 0.33826,-0.85201 1.8146,-1.6368"
fill="#2c2c2c"
id="path188"
style="stroke-width:0.830522" />
<path
d="m 105.07,29.784 c 0,0 0.0239,0.117 0.065,0.32088 0,0 0.53125,1.3138 0.92789,2.5669 0.4943,0.22554 0.9974,0.24292 2.4001,0.26887 1.8167,0.03255 2.3327,0.39235 3.0504,0.41413 0.71751,0.02155 1.5023,-0.83047 2.5105,-0.83047 v -2.1745 c -3.7333,0 -6.6537,-0.21037 -8.954,-0.56588"
fill="#1e1e1e"
id="path190"
style="stroke-width:0.830522" />
<path
d="m 105.13,30.105 c 0.0737,0.38158 0.21037,1.0602 0.36861,1.8189 0.0325,0.16056 0.0672,0.32517 0.10194,0.49001 0.16693,0.10843 0.30999,0.19299 0.45734,0.25798 -0.39664,-1.2532 -0.92789,-2.5669 -0.92789,-2.5669"
fill="#878787"
id="path192"
style="stroke-width:0.830522" />
<path
d="m 106.03,12.9 -0.40324,4.529 c 0,0 0.67431,-3.3193 0.91931,-4.284 0.2384,-0.93229 1.2358,-1.8255 1.2986,-1.8818 -1.4764,0.78482 -1.8146,1.6368 -1.8146,1.6368"
fill="#4d4d4d"
id="path194"
style="stroke-width:0.830522" />
<path
d="m 106.06,32.672 c -0.14735,-0.06499 -0.29041,-0.14955 -0.45734,-0.25798 0.24918,1.1946 0.52024,2.4629 0.6742,3.0353 0.0866,0.32528 0.40753,0.8651 0.8607,1.496 0.3079,0.42919 0.67639,0.89974 1.0732,1.3745 0.74799,0.89117 1.5979,1.7974 2.3458,2.4607 -0.12349,-0.12349 -1.2769,-1.2834 -2.2764,-2.851 -1.058,-1.6563 -1.5935,-2.5322 -1.8624,-3.9241 -0.0737,-0.38587 -0.20597,-0.8541 -0.35772,-1.3333"
fill="#fbfbfb"
id="path196"
style="stroke-width:0.830522" />
<path
d="m 112.5,41.921 c 0.60481,0.14306 1.2336,0.16693 1.5241,0.16693 v -3.1415 c -0.81089,0 -1.7756,-0.58755 -1.8363,-0.62438 0.0455,0.0044 0.60492,0.07159 1.8363,0.07159 v -0.88236 c -1.6304,0 -3.1848,-0.22334 -3.1848,-0.22334 0.83684,-0.01517 2.168,-0.35992 2.3783,-0.34254 0.20805,0.01726 0.80649,0.41622 0.80649,0.41622 v -1.7474 c -0.68068,0 -0.98859,-0.27107 -1.1274,-0.52256 -0.85421,-0.1821 -0.94746,-0.75019 -0.94746,-0.75019 0.14746,0.20168 0.33605,0.34903 0.83475,0.32957 0,0 0.44876,0.62879 1.2401,0.62879 v -2.7752 c -1.0082,0 -1.793,0.85201 -2.5105,0.83047 -0.71776,-0.02178 -1.2337,-0.38158 -3.0504,-0.41413 -1.4027,-0.02595 -1.9058,-0.04333 -2.4001,-0.26887 0.15175,0.47924 0.28404,0.94747 0.35772,1.3333 0.26887,1.3919 0.8044,2.2678 1.8624,3.9241 0.99948,1.5676 2.1529,2.7275 2.2764,2.851 0.007,0.0044 0.0109,0.0088 0.0109,0.0088 0.48132,0.42699 0.91711,0.75227 1.2552,0.90831 0.20597,0.09545 0.438,0.16913 0.67432,0.22334 z m -0.63307,-7.5643 c -0.69377,0.17341 -1.5134,1.3268 -1.6282,2.2375 -0.0131,0.16913 -0.0131,0.27536 -0.0131,0.27536 -0.004,-0.08897 0,-0.18002 0.0131,-0.27536 0.0217,-0.33826 0.0802,-0.92569 0.2406,-1.3853 0.20377,-0.57886 1.11,-0.79792 1.3876,-0.85213"
fill="#878787"
id="path198"
style="stroke-width:0.830522" />
<path
d="m 107.14,36.945 c -0.0997,1.1273 -0.0933,2.6688 -0.0152,4.3968 0.0846,0.56148 0.19079,1.097 0.3035,1.5869 0.3751,1.6305 0.82387,2.7774 0.82387,2.7774 0.84332,-1.173 2.0488,-2.2028 2.9616,-2.8922 0.34035,-0.25589 0.63956,-0.46614 0.86279,-0.6157 -1.6737,-0.52685 -2.6189,-1.8364 -3.8634,-3.8787 -0.39676,-0.47472 -0.76525,-0.94526 -1.0732,-1.3745"
fill="#fbfbfb"
id="path200"
style="stroke-width:0.830522" />
<path
d="m 107.42,42.929 c -0.11272,-0.48989 -0.21894,-1.0254 -0.30351,-1.5869 0.22983,5.1708 1.0883,12 1.6326,14.329 0.34903,0.07597 0.70247,0.14525 1.058,0.20593 -1.1122,-3.0938 -2.3502,-12.648 -2.387,-12.948"
fill="#878787"
id="path202"
style="stroke-width:0.830522" />
<path
d="m 108.25,45.706 c 0,0 -0.44877,-1.1469 -0.82386,-2.7774 0.0368,0.29922 1.2748,9.8538 2.387,12.948 0.36849,0.0629 0.7393,0.11933 1.1166,0.16485 0,0 0.48989,-4.9084 0.64604,-6.7621 0.15824,-1.8537 1.0406,-4.2321 1.0406,-4.2321 l -0.81077,-1.524 -0.22983,-0.27315 c 0,0 -2.0402,1.2531 -3.3258,2.4564"
fill="#fbfbfb"
id="path204"
style="stroke-width:0.830522" />
<path
d="m 110.55,40.78 c -0.74787,-0.66331 -1.5978,-1.5695 -2.3458,-2.4607 1.2445,2.0423 2.1898,3.3519 3.8634,3.8787 0.24721,-0.16704 0.40116,-0.26458 0.42282,-0.27756 -0.23631,-0.05421 -0.46834,-0.12789 -0.67431,-0.22334 -0.33814,-0.15604 -0.77394,-0.48132 -1.2553,-0.90831 0,0 -0.004,-0.0044 -0.0109,-0.0088"
fill="#2c2c2c"
id="path206"
style="stroke-width:0.830522" />
<path
d="m 110.47,35.209 c -0.16044,0.45966 -0.21894,1.0471 -0.2406,1.3853 0.1148,-0.91063 0.93438,-2.0641 1.6282,-2.2375 -0.27755,0.05421 -1.1838,0.27327 -1.3876,0.85213"
fill="#2c2c2c"
id="path208"
style="stroke-width:0.830522" />
<g
fill="#1e1e1e"
id="g216"
style="stroke-width:0.830522">
<path
d="m 108.25,45.706 c 1.2856,-1.2034 3.3258,-2.4564 3.3258,-2.4564 l -0.3642,-0.4358 c -0.91283,0.68937 -2.1183,1.7192 -2.9616,2.8922"
id="path210"
style="stroke-width:0.830531" />
<path
d="m 110.34,10.456 c 0,0 0.24501,2.0185 3.677,2.0185 v -1.0537 c -2.1984,0 -2.9572,-0.45097 -3.677,-0.96484"
id="path212"
style="stroke-width:0.830531" />
<path
d="m 110.93,56.041 c 0.19067,0.02388 0.38146,0.04542 0.57445,0.06498 0,0 0.24501,-5.2576 0.54422,-7.3496 0.29922,-2.0943 0.98639,-3.768 0.98639,-3.768 l -1.2292,-1.4656 0.81077,1.524 c 0,0 -0.88236,2.3785 -1.0406,4.2321 -0.15615,1.8537 -0.64604,6.7621 -0.64604,6.7621"
id="path214"
style="stroke-width:0.830531" />
</g>
<g
fill="#2c2c2c"
id="g224"
style="stroke-width:0.830522">
<path
d="m 111.21,42.814 0.3642,0.4358 0.22983,0.27315 1.2292,1.4656 h 0.98651 v -2.528 c -0.75448,0 -1.392,-0.08885 -1.947,-0.26226 -0.22322,0.14955 -0.52244,0.3598 -0.86278,0.6157"
id="path218"
style="stroke-width:0.830531" />
<path
d="m 111.5,56.106 c 0.14098,0.0152 0.28184,0.02815 0.42282,0.03696 l 0.002,-0.03696 c -0.12128,-2.1768 0.34046,-6.9616 0.34046,-6.9616 0,0 0.0477,5.8733 0.87367,7.0721 0.29054,0.01085 0.58327,0.01516 0.87808,0.01516 v -11.243 h -0.9865 c 0,0 -0.68717,1.6737 -0.98639,3.768 -0.29922,2.0921 -0.54422,7.3496 -0.54422,7.3496"
id="path220"
style="stroke-width:0.830531" />
<path
d="m 110.83,37.288 c 0,0 1.5545,0.22334 3.1848,0.22334 v -0.14967 c 0,0 -0.59844,-0.39896 -0.80649,-0.41622 -0.21037,-0.01738 -1.5415,0.32737 -2.3783,0.34254"
id="path222"
style="stroke-width:0.830531" />
</g>
<g
fill="#1e1e1e"
id="g232"
style="stroke-width:0.830522">
<path
d="m 112.27,49.145 c 0,0 -0.46174,4.7848 -0.34045,6.9616 l -0.002,0.03696 c 0.40104,0.03453 0.80649,0.0606 1.2162,0.07358 -0.82594,-1.1989 -0.87367,-7.0721 -0.87367,-7.0721"
id="path226"
style="stroke-width:0.830531" />
<path
d="m 111.94,34.341 c 0,0 0.0932,0.56809 0.94746,0.75019 -0.1192,-0.21906 -0.11271,-0.42062 -0.11271,-0.42062 -0.4987,0.01946 -0.68729,-0.12789 -0.83475,-0.32957"
id="path228"
style="stroke-width:0.830531" />
<path
d="m 112.5,41.921 c -0.0217,0.01297 -0.17561,0.11051 -0.42282,0.27756 0.555,0.17341 1.1925,0.26226 1.947,0.26226 v -0.37289 c -0.29053,0 -0.91932,-0.02386 -1.5241,-0.16693"
id="path230"
style="stroke-width:0.830531" />
</g>
<path
d="m 114.02,38.394 c -1.2314,0 -1.7908,-0.06719 -1.8363,-0.07159 0.0607,0.03684 1.0254,0.62438 1.8363,0.62438 v -0.5528"
fill="#878787"
id="path234"
style="stroke-width:0.830522" />
<path
d="m 112.89,35.092 c 0.13878,0.25149 0.44669,0.52256 1.1274,0.52256 v -0.31439 c -0.79132,0 -1.2401,-0.62879 -1.2401,-0.62879 0,0 -0.007,0.20156 0.11271,0.42062"
fill="#1e1e1e"
id="path236"
style="stroke-width:0.830522" />
<path
d="m 111.05,37.405 c 0.4,0.18685 0.28775,0.51862 1.1027,0.51723 0.81494,-0.0014 1.6597,-0.12604 1.6597,-0.12604 l 1.9737,-0.22299 1.2146,-0.23956 -0.0483,-0.01842 c 0,0 -1.4287,0.19566 -2.931,0.19566 -1.5024,0 -3.1545,-0.21801 -3.1545,-0.21801 l 0.18315,0.11213"
fill="#fbfbfb"
id="path238"
style="stroke-width:0.830522" />
<path
d="m 115.39,37.619 c -0.34034,-2.31e-4 -0.79119,-0.0027 -1.2459,-0.01065 -0.91932,-0.01657 -1.628,-0.04564 -3.1612,-0.30061 -0.003,-3.61e-4 -0.006,-6.89e-4 -0.008,-0.0011 h -0.0834 l 0.16183,0.09904 c 0.39988,0.18685 0.28764,0.51862 1.1026,0.51723 0.81494,-0.0014 1.6597,-0.12604 1.6597,-0.12604 l 1.5749,-0.17793"
fill="#1e1e1e"
id="path240"
style="stroke-width:0.830522" />
<path
d="m 114.76,33.811 c 0.19843,-0.02491 0.30258,0.79872 0.0262,0.79826 -0.27651,-3.61e-4 -0.30327,-0.76339 -0.0262,-0.79826"
fill="#fbfbfb"
id="path242"
style="stroke-width:0.830522" />
<path
d="m 115.61,37.779 c 0.18801,-0.20689 0.68856,-0.07842 0.48237,0.05618 -0.2062,0.13449 -0.63516,0.11202 -0.48237,-0.05618"
fill="#fbfbfb"
id="path244"
style="stroke-width:0.830522" />
<g
fill="#1e1e1e"
id="g254"
style="stroke-width:0.830522">
<path
d="m 114.56,36.174 c 0.25334,-0.26493 0.75134,-0.17944 1.2784,0.0673 0.52708,0.24674 1.5588,0.62798 1.5139,0.74011 -0.0219,0.05479 -0.28022,0.01529 -0.3203,0.01251 -0.23759,-0.0168 -0.47448,-0.04645 -0.71045,-0.07854 -0.27524,-0.0373 -0.5506,-0.0746 -0.82398,-0.12349 -0.22172,-0.03962 -0.46719,-0.06893 -0.67594,-0.15615 -0.14098,-0.05896 -0.42317,-0.29273 -0.26168,-0.46174"
id="path246"
style="stroke-width:0.830531" />
<path
d="m 113.47,36.174 c -0.25334,-0.26493 -0.75135,-0.17944 -1.2784,0.0673 -0.52708,0.24674 -1.5586,0.62798 -1.5138,0.74011 0.0219,0.05479 0.28022,0.01529 0.32019,0.01251 0.23771,-0.0168 0.4746,-0.04645 0.71057,-0.07854 0.27524,-0.0373 0.55048,-0.0746 0.82398,-0.12349 0.2216,-0.03962 0.46719,-0.06893 0.67593,-0.15615 0.14086,-0.05896 0.42317,-0.29273 0.26157,-0.46174"
id="path248"
style="stroke-width:0.830531" />
<path
d="m 112.5,37.797 c 0.78796,0.06754 3.7608,-0.27478 4.7098,-0.50866 0,0 -0.82514,0.3532 -2.0576,0.41842 -1.2323,0.06533 -2.8111,0.44448 -2.8111,0.44448 l 0.15893,-0.35424"
id="path250"
style="stroke-width:0.830531" />
<path
d="m 110.83,37.288 c 0,0 0.30084,0.27616 0.69783,0.57133 l 0.16044,-0.12001 -0.72088,-0.4329 L 110.83,37.288"
id="path252"
style="stroke-width:0.830531" />
</g>
<path
d="m 113.19,38.811 c 0.72887,-0.06742 2.3548,-0.25798 2.3941,-0.12337 0.0393,0.13449 -0.96994,0.56067 -1.5307,0.56067 -0.56067,0 -0.86336,-0.4373 -0.86336,-0.4373"
fill="#2c2c2c"
id="path256"
style="stroke-width:0.830522" />
<path
d="m 116.74,37.258 c 0.0924,-0.04935 0.45097,-0.03024 0.51828,0.02502 0.0673,0.05537 0.40058,0.45375 0.19785,0.47958 C 117.37193,37.77337 116.74,37.258 116.74,37.258"
fill="#1e1e1e"
id="path258"
style="stroke-width:0.830522" />
<path
d="m 111.3,37.258 c -0.0924,-0.04935 -0.45108,-0.03024 -0.51827,0.02502 -0.0673,0.05537 -0.40069,0.45375 -0.19797,0.47958 0.0843,0.01077 0.71624,-0.5046 0.71624,-0.5046"
fill="#1e1e1e"
id="path260"
style="stroke-width:0.830522" />
<path
d="m 125.14,48.41 -2.0562,-4.8164 c 0,0 0.9419,3.1908 1.3009,3.7345 0.35877,0.54353 0.75529,1.082 0.75529,1.082"
fill="#878787"
id="path262"
style="stroke-width:0.830522" />
<path
d="m 101.2,42.088 c 0,0 1.5377,-2.5348 1.7444,-2.7826 0.20678,-0.24778 -0.33153,0.77614 -0.64546,1.4752 -0.31393,0.69922 -1.099,1.3074 -1.099,1.3074"
fill="#878787"
id="path264"
style="stroke-width:0.830522" />
</g>
<path
d="m 112.5,37.797 -3.3092,2.7712 c 0,0 0.0879,0.13519 0.0716,0.29146 l 3.2376,-2.8745 0.10044,-0.18222 -0.10044,-0.0059"
fill="#1f1f1f"
id="path268"
style="stroke-width:0.830522" />
<path
d="m 112.51,37.938 c -0.0941,0.26006 -2.6772,2.5031 -3.3217,3.131 v 0.0028 c -0.16311,0.15766 -0.26273,0.25439 -0.26273,0.25439 -0.011,0 -0.49511,-0.0027 -0.82155,-0.4563 -0.33188,-0.45642 -0.12719,-0.68601 -0.12719,-0.68601 l 0.177,-0.12731 h 0.003 l 3.6755,-2.7496 c 0.37069,-0.12441 0.79131,0.31022 0.6779,0.6311"
fill="#2d2d2d"
id="path270"
style="stroke-width:0.830522" />
<path
d="m 108.55,39.952 3.2466,-2.4411 -2.9334,2.5915 z"
fill="#4e4e4e"
id="path272"
style="stroke-width:0.830522" />
<path
d="m 109.19,41.069 v 0.0028 c -0.16311,0.15766 -0.26273,0.25439 -0.26273,0.25439 -0.011,0 -0.49511,-0.0027 -0.82155,-0.4563 -0.30964,-0.42606 -0.15198,-0.65288 -0.12997,-0.68312 l 0.003,-0.0029 0.17701,-0.12731 h 0.003 c 0.35691,-0.07727 1.0036,0.63215 1.0317,1.0125"
fill="#fbfdfe"
id="path274"
style="stroke-width:0.830522" />
<path
d="m 108.92,41.326 c -0.011,0 -0.49511,-0.0027 -0.82155,-0.4563 -0.30964,-0.42606 -0.15198,-0.65288 -0.12997,-0.68312 l 0.003,-0.0029 c 0.11619,-0.08294 0.49233,0.11063 0.6888,0.38992 0.19357,0.28219 0.34845,0.61697 0.25994,0.75239"
fill="#ef2e2e"
id="path276"
style="stroke-width:0.830522" />
<g
fill="#fbfafa"
stroke-width="0.830531"
id="g282">
<path
d="m 106.07,38.86 c -0.55696,-0.45398 -1.0123,-1.0714 -1.1242,-1.7814 -0.12858,-0.80904 0.19427,-1.6117 0.36919,-2.4117 0.17492,-0.80139 0.15048,-1.7636 -0.48364,-2.2831 -0.7576,-0.61998 -2.0464,-0.3254 -2.6831,-1.0689 -0.44379,-0.51966 -0.34347,-1.3107 -0.0952,-1.9474 0.24953,-0.63805 0.62381,-1.2477 0.66111,-1.9307 0.0463,-0.84634 -0.45665,-1.6528 -1.1306,-2.1673 -0.67524,-0.51457 -1.5023,-0.78216 -2.3242,-0.99566 -0.62902,-0.16334 -1.2876,-0.30999 -1.8008,-0.70999 -0.13692,-0.10657 -0.25462,-0.23215 -0.35714,-0.36919 0.15546,0.37672 0.39722,0.7159 0.68763,1.0006 0.31347,0.30744 0.67733,0.56357 1.0726,0.75494 0.44518,0.21558 0.92627,0.3481 1.3756,0.55476 0.44924,0.20666 0.88167,0.50634 1.1044,0.94804 0.27038,0.53623 0.1835,1.191 -0.0485,1.7448 0.0356,-0.43742 -0.21489,-0.85885 -0.55153,-1.1403 -0.7408,-0.61952 -1.9171,-0.94133 -2.8468,-1.1157 -0.41738,-0.07831 -0.85074,-0.12673 -1.2261,-0.3254 -0.37533,-0.19867 -0.68511,-0.59716 -0.62755,-1.0179 -0.20092,0.8117 -0.03264,1.6839 0.33462,2.4352 0.23976,0.49059 0.56087,0.93635 0.91708,1.3486 0.34185,0.3956 0.64477,0.58558 1.0887,0.83464 0.78425,0.44008 1.7538,0.74683 2.1923,1.6013 0.19936,0.38981 0.25462,0.85155 0.51966,1.2001 0.27397,0.36142 0.70872,0.52488 1.1396,0.69713 0.29447,0.08873 0.56716,0.22126 0.80382,0.41298 -0.23411,-0.18141 -0.51827,-0.29841 -0.80382,-0.41298 -0.54156,-0.16588 -1.15,-0.18905 -1.7223,-0.18256 -0.9609,0.01031 -2.0027,0.03857 -2.7796,-0.52743 -0.71138,-0.51839 -0.99821,-1.3776 -1.33,-2.2175 -0.27522,-0.46302 -0.69328,-0.83985 -1.2026,-1.0032 -0.67916,-0.21871 -1.5049,0 -1.9102,0.5865 -0.27395,0.39618 -0.33698,0.90032 -0.32924,1.3814 0.0077,0.49916 0.08747,1.0071 0.32802,1.4445 0.34215,0.62253 0.97882,1.0405 1.6528,1.2657 1.3094,0.4373 2.9533,0.48618 4.3231,0.44379 0.36143,-0.0117 0.72424,-0.03858 1.0857,-0.04773 0.74729,-0.01934 1.6284,-0.02827 2.2895,0.37058 0.94793,0.56982 0.56079,1.8959 0.57886,2.8092 0.0116,0.62635 0.10809,1.2721 0.47843,1.7917 0.70362,0.98523 2.0298,1.5217 3.2067,1.5936 0.4168,0.02583 0.88618,-0.07449 1.0097,-0.50414 -0.64304,-0.30234 -1.2914,-0.61106 -1.8419,-1.0599"
id="path278"
style="stroke-width:0.830531" />
<path
d="m 101.23,34.598 c 0.0467,0.0065 0.0932,0.01402 0.13994,0.02259 0.25056,0.04622 0.50472,0.13125 0.68647,0.30988 0.26644,0.26168 0.30999,0.64257 0.33386,0.99612 0.0273,0.40626 0.0195,0.83753 0.22809,1.1873 -0.057,-0.09545 -0.18859,-0.16982 -0.25485,-0.27095 -0.0778,-0.11874 -0.13322,-0.25056 -0.18315,-0.38286 -0.1,-0.26516 -0.16797,-0.54596 -0.30848,-0.79421 -0.35031,-0.61894 -1.1386,-0.65995 -1.7705,-0.53982 -0.53831,0.1024 -1.0681,0.28057 -1.5987,0.41147 -0.56924,0.1404 -1.1599,0.21674 -1.7445,0.25647 -0.27778,0.01877 -0.57598,0.02247 -0.81042,-0.12766 -0.19427,-0.12441 -0.31884,-0.35077 -0.31997,-0.58141 -0.0026,-0.5075 0.30223,-0.5572 0.6908,-0.48595 0.67532,0.12407 1.3504,0.10785 2.0336,0.08595 0.49545,-0.01564 0.99068,-0.04031 1.4852,-0.07391 0.46174,-0.03151 0.93159,-0.08816 1.3926,-0.01297"
id="path280"
style="stroke-width:0.830531" />
</g>
</g>
<rect
x="0.0054588001"
y="0.066145003"
width="137.58"
height="63.5"
fill="#333333"
fill-rule="evenodd"
opacity="0.56765"
id="rect286"
style="stroke-width:0.999978" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 42 KiB

74
data/vectors/sci-fi.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 368 KiB

133
data/vectors/travel.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 57 KiB

View file

@ -1,4 +1,4 @@
/* koto-action-bar.c
/* action-bar.c
*
* Copyright 2021 Joshua Strobl
*
@ -17,7 +17,6 @@
#include <glib-2.0/glib.h>
#include <gtk-4.0/gtk/gtk.h>
#include "koto-action-bar.h"
#include "../config/config.h"
#include "../db/cartographer.h"
#include "../indexer/album-playlist-funcs.h"
@ -25,9 +24,10 @@
#include "../playlist/add-remove-track-popover.h"
#include "../playlist/current.h"
#include "../playback/engine.h"
#include "../koto-button.h"
#include "../koto-utils.h"
#include "../koto-window.h"
#include "action-bar.h"
#include "button.h"
extern KotoAddRemoveTrackPopover * koto_add_remove_track_popup;
extern KotoCartographer * koto_maps;
@ -263,12 +263,12 @@ void koto_action_bar_handle_play_track_button_clicked(
KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, self->current_album_uuid); // Get the Album
if (KOTO_IS_ALBUM(album)) { // Have an Album
playlist = koto_album_create_playlist(album); // Create our playlist dynamically for the Album
playlist = koto_album_get_playlist(album); // Create our playlist dynamically for the Album
}
}
if (KOTO_IS_PLAYLIST(playlist)) { // Is a playlist
koto_current_playlist_set_playlist(current_playlist, playlist, FALSE); // Update our playlist to the one associated with the track we are playing
koto_current_playlist_set_playlist(current_playlist, playlist, FALSE, FALSE); // Update our playlist to the one associated with the track we are playing
koto_playlist_set_track_as_current(playlist, koto_track_get_uuid(track)); // Get this track as the current track in the position
}
@ -295,7 +295,7 @@ void koto_action_bar_handle_remove_from_playlist_button_clicked(
goto doclose;
}
if (!koto_utils_is_string_valid(self->current_playlist_uuid)) { // Not valid UUID
if (!koto_utils_string_is_valid(self->current_playlist_uuid)) { // Not valid UUID
goto doclose;
}
@ -325,11 +325,11 @@ void koto_action_bar_set_tracks_in_album_selection(
return;
}
if (koto_utils_is_string_valid(self->current_album_uuid)) { // Album UUID currently set
if (koto_utils_string_is_valid(self->current_album_uuid)) { // Album UUID currently set
g_free(self->current_album_uuid);
}
if (koto_utils_is_string_valid(self->current_playlist_uuid)) { // Playlist UUID currently set
if (koto_utils_string_is_valid(self->current_playlist_uuid)) { // Playlist UUID currently set
g_free(self->current_playlist_uuid);
}
@ -359,11 +359,11 @@ void koto_action_bar_set_tracks_in_playlist_selection(
return;
}
if (koto_utils_is_string_valid(self->current_album_uuid)) { // Album UUID currently set
if (koto_utils_string_is_valid(self->current_album_uuid)) { // Album UUID currently set
g_free(self->current_album_uuid);
}
if (koto_utils_is_string_valid(self->current_playlist_uuid)) { // Playlist UUID currently set
if (koto_utils_string_is_valid(self->current_playlist_uuid)) { // Playlist UUID currently set
g_free(self->current_playlist_uuid);
}

View file

@ -1,4 +1,4 @@
/* koto-action-bar.h
/* action-bar.h
*
* Copyright 2021 Joshua Strobl
*

289
src/components/album-info.c Normal file
View file

@ -0,0 +1,289 @@
/* album-info.c
*
* Copyright 2021 Joshua Strobl
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <glib-2.0/glib.h>
#include <gtk-4.0/gtk/gtk.h>
#include "../config/config.h"
#include "../db/cartographer.h"
#include "../indexer/structs.h"
#include "../koto-utils.h"
#include "album-info.h"
extern KotoCartographer * koto_maps;
extern KotoConfig * config;
enum {
PROP_0,
PROP_TYPE,
N_PROPERTIES
};
static GParamSpec * props[N_PROPERTIES] = {
NULL,
};
struct _KotoAlbumInfo {
GtkBox parent_instance;
KotoAlbumInfoType type;
KotoAlbum * album;
GtkWidget * name_year_box;
GtkWidget * genres_tags_list;
GtkWidget * name_label;
GtkWidget * year_badge;
GtkWidget * narrator_label;
GtkWidget * description_label;
};
struct _KotoAlbumInfoClass {
GtkBoxClass parent_class;
};
G_DEFINE_TYPE(KotoAlbumInfo, koto_album_info, GTK_TYPE_BOX);
static void koto_album_info_set_property(
GObject * obj,
guint prop_id,
const GValue * val,
GParamSpec * spec
);
static void koto_album_info_class_init(KotoAlbumInfoClass * c) {
GObjectClass * gobject_class;
gobject_class = G_OBJECT_CLASS(c);
gobject_class->set_property = koto_album_info_set_property;
props[PROP_TYPE] = g_param_spec_string(
"type",
"Type of AlbumInfo component",
"Type of AlbumInfo component",
"album",
G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_WRITABLE
);
g_object_class_install_properties(gobject_class, N_PROPERTIES, props);
}
static void koto_album_info_init(KotoAlbumInfo * self) {
gtk_widget_add_css_class(GTK_WIDGET(self), "album-info");
self->type = KOTO_ALBUM_INFO_TYPE_ALBUM; // Default to Album here
self->name_year_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); // Create out name box for the name and year
gtk_widget_add_css_class(self->name_year_box, "album-title-year-combo");
self->name_label = gtk_label_new(NULL); // Create our name label
gtk_widget_set_halign(self->name_label, GTK_ALIGN_START);
gtk_widget_set_valign(self->name_label, GTK_ALIGN_START);
gtk_widget_add_css_class(self->name_label, "album-title");
self->year_badge = gtk_label_new(NULL); // Create our year badge
gtk_widget_add_css_class(self->year_badge, "album-year");
gtk_widget_add_css_class(self->year_badge, "label-badge");
gtk_widget_set_valign(self->year_badge, GTK_ALIGN_CENTER); // Center vertically align the year badge
self->narrator_label = gtk_label_new(NULL); // Create our narrator label
gtk_widget_add_css_class(self->narrator_label, "album-narrator");
gtk_widget_set_halign(self->narrator_label, GTK_ALIGN_START);
self->description_label = gtk_label_new(NULL); // Create our description label
gtk_widget_add_css_class(self->description_label, "album-description");
gtk_widget_set_halign(self->description_label, GTK_ALIGN_START);
gtk_box_append(GTK_BOX(self->name_year_box), self->name_label); // Add the name label to the name + year box
gtk_box_append(GTK_BOX(self->name_year_box), self->year_badge); // Add the year badge to the name + year box
gtk_box_append(GTK_BOX(self), self->name_year_box);
gtk_box_append(GTK_BOX(self), self->narrator_label);
gtk_box_append(GTK_BOX(self), self->description_label);
g_signal_connect(config, "notify::ui-info-show-description", G_CALLBACK(koto_album_info_apply_configuration_state), self);
g_signal_connect(config, "notify::ui-info-show-genres", G_CALLBACK(koto_album_info_apply_configuration_state), self);
g_signal_connect(config, "notify::ui-info-show-narrator", G_CALLBACK(koto_album_info_apply_configuration_state), self);
g_signal_connect(config, "notify::ui-info-show-year", G_CALLBACK(koto_album_info_apply_configuration_state), self);
}
static void koto_album_info_set_property(
GObject * obj,
guint prop_id,
const GValue * val,
GParamSpec * spec
) {
KotoAlbumInfo * self = KOTO_ALBUM_INFO(obj);
switch (prop_id) {
case PROP_TYPE:
koto_album_info_set_type(self, g_value_get_string(val));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
break;
}
}
void koto_album_info_apply_configuration_state(
KotoConfig * c,
guint prop_id,
KotoAlbumInfo * self
) {
(void) c;
(void) prop_id;
gboolean show_description = TRUE;
gboolean show_genres = TRUE;
gboolean show_narrator = TRUE;
gboolean show_year = TRUE;
g_object_get(
config,
"ui-album-info-show-description",
&show_description,
"ui-album-info-show-genres",
&show_genres,
"ui-album-info-show-narrator",
&show_narrator,
"ui-album-info-show-year",
&show_year,
NULL
);
if (self->type != KOTO_ALBUM_INFO_TYPE_AUDIOBOOK) { // If the type is NOT an audiobook
show_narrator = FALSE; // Narrator should never be shown. Only really applicable to audiobook
}
if (self->type == KOTO_ALBUM_INFO_TYPE_PODCAST) { // If the type is podcast
show_year = FALSE; // Year isn't really applicable to podcast, at least not the "global" year
}
if (koto_utils_string_is_valid(koto_album_get_description(self->album)) && show_description) { // Have description to show in the first place, and should show it
gtk_widget_show(self->description_label);
} else { // Don't have content, just hide it
gtk_widget_hide(self->description_label);
}
if (!KOTO_IS_ALBUM(self->album)) { // Don't have an album defined
return;
}
if (GTK_IS_FLOW_BOX(self->genres_tags_list)) {
if ((g_list_length(koto_album_get_genres(self->album)) > 0) && show_genres) { // Have genres to show in the first place, and should show it
gtk_widget_show(self->genres_tags_list);
} else {
gtk_widget_hide(self->genres_tags_list);
}
}
if (koto_utils_string_is_valid(koto_album_get_narrator(self->album)) && show_narrator) { // Have narrator and should show it
gtk_widget_show(self->narrator_label);
} else {
gtk_widget_hide(self->narrator_label);
}
if ((koto_album_get_year(self->album) != 0) && show_year) { // Have year and should show it
gtk_widget_show(self->year_badge);
} else {
gtk_widget_hide(self->year_badge);
}
}
void koto_album_info_set_album_uuid(
KotoAlbumInfo * self,
gchar * album_uuid
) {
if (!KOTO_IS_ALBUM_INFO(self)) {
return;
}
if (!koto_utils_string_is_valid(album_uuid)) {
return;
}
KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, album_uuid); // Get the album
if (!KOTO_IS_ALBUM(album)) { // Is not an album
return;
}
self->album = album;
gtk_label_set_text(GTK_LABEL(self->name_label), koto_album_get_name(self->album)); // Set the name label text to the album
gtk_label_set_text(GTK_LABEL(self->year_badge), g_strdup_printf("%li", koto_album_get_year(self->album))); // Set the year label text to any year for the album
gchar * narrator = koto_album_get_narrator(self->album);
if (koto_utils_string_is_valid(narrator)) { // Have a narrator
gtk_label_set_text(GTK_LABEL(self->narrator_label), g_strdup_printf("Narrated by %s", koto_album_get_narrator(self->album))); // Set narrated by string
}
gchar * description = koto_album_get_description(self->album);
if (koto_utils_string_is_valid(description)) { // Have a description
gtk_label_set_markup(GTK_LABEL(self->description_label), description); // Set the markup so we treat it more like HTML and let pango do its thing
}
if (GTK_IS_BOX(self->genres_tags_list)) { // Genres Flow
gtk_box_remove(GTK_BOX(self), self->genres_tags_list); // Remove the genres flow from the box
}
self->genres_tags_list = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); // Create our flow box for the genres
gtk_widget_add_css_class(self->genres_tags_list, "genres-tag-list");
GList * album_genres = koto_album_get_genres(self->album);
if (g_list_length(album_genres) != 0) { // Have genres
GList * cur_list;
for (cur_list = album_genres; cur_list != NULL; cur_list = cur_list->next) { // Iterate over each genre
GtkWidget * genre_label = gtk_label_new(koto_utils_string_title(cur_list->data));
gtk_widget_add_css_class(genre_label, "label-badge"); // Add label badge styling
gtk_box_append(GTK_BOX(self->genres_tags_list), genre_label); // Append to the genre tags list
}
} else {
gtk_widget_hide(self->genres_tags_list); // Hide the genres tag list
}
gtk_box_insert_child_after(GTK_BOX(self), self->genres_tags_list, self->name_year_box); // Add after the name+year flowbox
koto_album_info_apply_configuration_state(NULL, 0, self); // Apply our configuration state immediately so we know what to show and hide for this album based on the config
}
void koto_album_info_set_type(
KotoAlbumInfo * self,
const gchar * type
) {
if (!KOTO_IS_ALBUM_INFO(self)) {
return;
}
if (g_strcmp0(type, "audiobook") == 0) { // If this is an audiobook
self->type = KOTO_ALBUM_INFO_TYPE_AUDIOBOOK;
} else if (g_strcmp0(type, "podcast") == 0) { // If this is a podcast
self->type = KOTO_ALBUM_INFO_TYPE_PODCAST;
} else {
self->type = KOTO_ALBUM_INFO_TYPE_ALBUM;
}
}
KotoAlbumInfo * koto_album_info_new(gchar * type) {
return g_object_new(
KOTO_TYPE_ALBUM_INFO,
"orientation",
GTK_ORIENTATION_VERTICAL,
"type",
type,
NULL
);
}

View file

@ -0,0 +1,52 @@
/* album-info.h
*
* Copyright 2021 Joshua Strobl
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <glib-2.0/glib-object.h>
#include <gtk-4.0/gtk/gtk.h>
#include "../config/config.h"
G_BEGIN_DECLS
typedef enum {
KOTO_ALBUM_INFO_TYPE_ALBUM,
KOTO_ALBUM_INFO_TYPE_AUDIOBOOK,
KOTO_ALBUM_INFO_TYPE_PODCAST
} KotoAlbumInfoType;
#define KOTO_TYPE_ALBUM_INFO (koto_album_info_get_type())
G_DECLARE_FINAL_TYPE(KotoAlbumInfo, koto_album_info, KOTO, ALBUM_INFO, GtkBox);
#define KOTO_IS_ALBUM_INFO(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_ALBUM_INFO))
void koto_album_info_apply_configuration_state(
KotoConfig * c,
guint prop_id,
KotoAlbumInfo * self
);
void koto_album_info_set_album_uuid(
KotoAlbumInfo * self,
gchar * album_uuid
);
void koto_album_info_set_type(
KotoAlbumInfo * self,
const gchar * type
);
KotoAlbumInfo * koto_album_info_new(gchar * type);

View file

@ -16,9 +16,12 @@
*/
#include <gtk-4.0/gtk/gtk.h>
#include "koto-button.h"
#include "config/config.h"
#include "koto-utils.h"
#include "button.h"
#include "../koto-window.h"
#include "../koto-utils.h"
extern KotoWindow * main_window;
struct _PixbufSize {
guint size;
@ -70,6 +73,8 @@ struct _KotoButton {
GtkBox parent_instance;
guint pix_size;
gpointer arbitrary_data;
GtkWidget * button_pic;
GtkWidget * badge_label;
GtkWidget * button_label;
@ -313,6 +318,37 @@ void koto_button_flip(KotoButton * self) {
koto_button_show_image(self, !self->currently_showing_alt);
}
gpointer koto_button_get_data(KotoButton * self) {
return self->arbitrary_data;
}
void koto_button_global_page_nav_callback(
GtkGestureClick * gesture,
int n_press,
double x,
double y,
gpointer user_data
) {
(void) gesture;
(void) n_press;
(void) x;
(void) y;
KotoButton * btn = KOTO_BUTTON(user_data); // Cast our user data as a button
if (!KOTO_IS_BUTTON(btn)) { // Not a button
return;
}
gchar * btn_nav_uuid = (gchar*) koto_button_get_data(btn); // Get the data
if (!koto_utils_string_is_valid(btn_nav_uuid)) { // Not a valid string
return;
}
koto_window_go_to_page(main_window, btn_nav_uuid);
}
void koto_button_handle_mouse_enter(
GtkEventControllerMotion * controller,
double x,
@ -357,6 +393,17 @@ void koto_button_hide_image(KotoButton * self) {
}
}
void koto_button_set_data(
KotoButton * self,
gpointer data
) {
if (!KOTO_IS_BUTTON(self)) { // Not a button
return;
}
self->arbitrary_data = data;
}
void koto_button_set_pseudoactive_styling(KotoButton * self) {
if (!KOTO_IS_BUTTON(self)) { // Not a button
return;
@ -408,14 +455,19 @@ void koto_button_set_file_path(
return;
}
if (!koto_utils_is_string_valid(file_path)) { // file path is invalid
if (!koto_utils_string_is_valid(file_path)) { // file path is invalid
return;
}
if (koto_utils_is_string_valid(self->image_file_path)) { // image file path is valid
if (g_strcmp0(self->image_file_path, file_path) == 0) { // Request setting as same file
return;
}
if (koto_utils_string_is_valid(self->image_file_path)) { // image file path is valid
g_free(self->image_file_path);
}
self->use_from_file = TRUE;
self->image_file_path = g_strdup(file_path);
koto_button_show_image(self, FALSE);
}
@ -429,7 +481,7 @@ void koto_button_set_icon_name(
return;
}
if (!koto_utils_is_string_valid(icon_name)) { // Not a valid icon
if (!koto_utils_string_is_valid(icon_name)) { // Not a valid icon
return;
}
@ -517,18 +569,18 @@ void koto_button_set_text(
return;
}
if (!koto_utils_is_string_valid(text)) { // Invalid text
if (!koto_utils_string_is_valid(text)) { // Invalid text
return;
}
if (koto_utils_is_string_valid(self->text)) { // Text defined
if (koto_utils_string_is_valid(self->text)) { // Text defined
g_free(self->text); // Free existing text
}
self->text = g_strdup(text);
if (GTK_IS_LABEL(self->button_label)) { // If we have a button label
if (koto_utils_is_string_valid(self->text)) { // Have text set
if (koto_utils_string_is_valid(self->text)) { // Have text set
gtk_label_set_text(GTK_LABEL(self->button_label), self->text);
gtk_widget_show(self->button_label); // Show the label
} else { // Have a label but no longer text
@ -536,8 +588,9 @@ void koto_button_set_text(
g_free(self->button_label);
}
} else { // If we do not have a button label
if (koto_utils_is_string_valid(self->text)) { // If we have text
if (koto_utils_string_is_valid(self->text)) { // If we have text
self->button_label = gtk_label_new(self->text); // Create our label
gtk_widget_set_hexpand(self->button_label, TRUE);
gtk_label_set_xalign(GTK_LABEL(self->button_label), 0);
if (GTK_IS_IMAGE(self->button_pic)) { // If we have an image
@ -551,6 +604,45 @@ void koto_button_set_text(
g_object_notify_by_pspec(G_OBJECT(self), btn_props[PROP_TEXT]);
}
void koto_button_set_text_justify(
KotoButton * self,
GtkJustification j
) {
if (!KOTO_IS_BUTTON(self)) {
return;
}
if (!GTK_IS_LABEL(self->button_label)) { // If we do not have a button label
return;
}
if (j == GTK_JUSTIFY_CENTER) { // Center text
gtk_label_set_xalign(GTK_LABEL(self->button_label), 0.5);
} else if (j == GTK_JUSTIFY_RIGHT) { // Right align
gtk_label_set_xalign(GTK_LABEL(self->button_label), 1.0);
} else {
gtk_label_set_xalign(GTK_LABEL(self->button_label), 0);
}
gtk_label_set_justify(GTK_LABEL(self->button_label), j);
}
void koto_button_set_text_wrap(
KotoButton * self,
gboolean wrap
) {
if (!KOTO_IS_BUTTON(self)) {
return;
}
if (!GTK_IS_LABEL(self->button_label)) { // If we do not have a button label
return;
}
gtk_label_set_single_line_mode(GTK_LABEL(self->button_label), !wrap);
gtk_label_set_wrap(GTK_LABEL(self->button_label), wrap);
}
void koto_button_show_image(
KotoButton * self,
gboolean use_alt
@ -560,7 +652,7 @@ void koto_button_show_image(
}
if (self->use_from_file) { // Use from a file instead of icon name
if (!koto_utils_is_string_valid(self->image_file_path)) { // Not set
if (!koto_utils_string_is_valid(self->image_file_path)) { // Not set
return;
}

View file

@ -1,4 +1,4 @@
/* koto-button.h
/* button.h
*
* Copyright 2021 Joshua Strobl
*
@ -75,6 +75,16 @@ void koto_button_add_click_handler(
void koto_button_flip(KotoButton * self);
gpointer koto_button_get_data(KotoButton * self);
void koto_button_global_page_nav_callback(
GtkGestureClick * gesture,
int n_press,
double x,
double y,
gpointer user_data
);
void koto_button_handle_mouse_enter(
GtkEventControllerMotion * controller,
double x,
@ -89,6 +99,11 @@ void koto_button_handle_mouse_leave(
void koto_button_hide_image(KotoButton * self);
void koto_button_set_data(
KotoButton * self,
gpointer data
);
void koto_button_set_pseudoactive_styling(KotoButton * self);
void koto_button_set_badge_text(
@ -127,6 +142,16 @@ void koto_button_set_text(
gchar * text
);
void koto_button_set_text_justify(
KotoButton * self,
GtkJustification j
);
void koto_button_set_text_wrap(
KotoButton * self,
gboolean wrap
);
void koto_button_show_image(
KotoButton * self,
gboolean use_alt

View file

@ -1,4 +1,4 @@
/* koto-cover-art-button.c
/* cover-art-button.c
*
* Copyright 2021 Joshua Strobl
*
@ -17,9 +17,9 @@
#include <glib-2.0/glib.h>
#include <gtk-4.0/gtk/gtk.h>
#include "koto-cover-art-button.h"
#include "../koto-button.h"
#include "../koto-utils.h"
#include "button.h"
#include "cover-art-button.h"
struct _KotoCoverArtButton {
GObject parent_instance;
@ -29,6 +29,8 @@ struct _KotoCoverArtButton {
GtkWidget * revealer;
KotoButton * play_button;
gchar * art_path;
guint height;
guint width;
};
@ -119,6 +121,8 @@ static void koto_cover_art_button_init(KotoCoverArtButton * self) {
g_signal_connect(motion_controller, "enter", G_CALLBACK(koto_cover_art_button_show_overlay_controls), self);
g_signal_connect(motion_controller, "leave", G_CALLBACK(koto_cover_art_button_hide_overlay_controls), self);
gtk_widget_add_controller(self->main, motion_controller);
self->art_path = NULL;
}
static void koto_cover_art_button_get_property(
@ -194,13 +198,16 @@ void koto_cover_art_button_set_art_path(
return;
}
gboolean defined_artwork = koto_utils_is_string_valid(art_path);
gboolean defined_artwork = koto_utils_string_is_valid(art_path);
if (GTK_IS_IMAGE(self->art)) { // Already have an image
if (!defined_artwork) { // No art path or empty string
gtk_image_set_from_icon_name(GTK_IMAGE(self->art), "audio-x-generic-symbolic");
} else { // Have an art path
gtk_image_set_from_file(GTK_IMAGE(self->art), g_strdup(art_path)); // Set from the file
if (g_strcmp0(self->art_path, art_path) != 0) {
self->art_path = g_strdup(art_path); // Set our art path
gtk_image_set_from_file(GTK_IMAGE(self->art), self->art_path); // Set from the file
}
}
} else { // If we don't have an image
self->art = koto_utils_create_image_from_filepath(defined_artwork ? g_strdup(art_path) : NULL, "audio-x-generic-symbolic", self->width, self->height);

View file

@ -1,4 +1,4 @@
/* koto-cover-art-button.h
/* cover-art-button.h
*
* Copyright 2021 Joshua Strobl
*
@ -17,7 +17,7 @@
#include <glib-2.0/glib.h>
#include <gtk-4.0/gtk/gtk.h>
#include "../koto-button.h"
#include "button.h"
G_BEGIN_DECLS

View file

@ -1,4 +1,4 @@
/* koto-track-item.c
/* track-item.c
*
* Copyright 2021 Joshua Strobl
*
@ -18,8 +18,8 @@
#include <gtk-4.0/gtk/gtk.h>
#include "indexer/structs.h"
#include "playlist/add-remove-track-popover.h"
#include "koto-button.h"
#include "koto-track-item.h"
#include "button.h"
#include "track-item.h"
extern KotoAddRemoveTrackPopover * koto_add_remove_track_popup;

View file

@ -1,4 +1,4 @@
/* koto-track-item.h
/* track-item.h
*
* Copyright 2021 Joshua Strobl
*

View file

@ -21,10 +21,10 @@
#include "../playback/engine.h"
#include "../playlist/current.h"
#include "../playlist/playlist.h"
#include "../koto-button.h"
#include "../koto-utils.h"
#include "../koto-window.h"
#include "koto-action-bar.h"
#include "action-bar.h"
#include "button.h"
#include "track-table.h"
extern KotoActionBar * action_bar;
@ -122,14 +122,12 @@ void koto_track_table_bind_track_item(
return;
}
gchar * track_name = NULL;
gchar * track_name = koto_track_get_name(track);
gchar * album_uuid = NULL;
gchar * artist_uuid = NULL;
g_object_get(
track,
"parsed-name",
&track_name,
"album-uuid",
&album_uuid,
"artist-uuid",
@ -142,7 +140,7 @@ void koto_track_table_bind_track_item(
gtk_label_set_label(GTK_LABEL(track_position_label), g_strdup_printf("%u", track_position)); // Set the track position
gtk_label_set_label(GTK_LABEL(track_name_label), track_name); // Set our track name
if (koto_utils_is_string_valid(album_uuid)) { // Is associated with an album
if (koto_utils_string_is_valid(album_uuid)) { // Is associated with an album
KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, album_uuid);
if (KOTO_IS_ALBUM(album)) {
@ -150,10 +148,12 @@ void koto_track_table_bind_track_item(
}
}
KotoArtist * artist = koto_cartographer_get_artist_by_uuid(koto_maps, artist_uuid);
if (koto_utils_string_is_valid(artist_uuid)) { // Is associated with an artist
KotoArtist * artist = koto_cartographer_get_artist_by_uuid(koto_maps, artist_uuid);
if (KOTO_IS_ARTIST(artist)) {
gtk_label_set_label(GTK_LABEL(track_artist_label), koto_artist_get_name(artist)); // Get the name of the artist and set it to the label
if (KOTO_IS_ARTIST(artist)) {
gtk_label_set_label(GTK_LABEL(track_artist_label), koto_artist_get_name(artist)); // Get the name of the artist and set it to the label
}
}
GList * data = NULL;
@ -229,7 +229,7 @@ void koto_track_table_handle_track_album_clicked (
gtk_widget_add_css_class(GTK_WIDGET(self->track_album_button), "active");
koto_button_hide_image(self->track_num_button); // Go back to hiding the image
koto_track_table_set_playlist_model(self, KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ALBUM);
koto_track_table_set_playlist_model(self, KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_ALBUM);
}
void koto_track_table_handle_track_artist_clicked(
@ -247,7 +247,7 @@ void koto_track_table_handle_track_artist_clicked(
gtk_widget_add_css_class(GTK_WIDGET(self->track_artist_button), "active");
koto_button_hide_image(self->track_num_button); // Go back to hiding the image
koto_track_table_set_playlist_model(self, KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ARTIST);
koto_track_table_set_playlist_model(self, KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_ARTIST);
}
void koto_track_table_item_handle_clicked(
@ -276,7 +276,7 @@ void koto_track_table_item_handle_clicked(
KotoTrackTable * self = g_list_nth_data(data, 0);
gchar * track_uuid = g_list_nth_data(data, 1);
if (!koto_utils_is_string_valid(track_uuid)) { // Not a valid string
if (!koto_utils_string_is_valid(track_uuid)) { // Not a valid string
return;
}
@ -284,7 +284,7 @@ void koto_track_table_item_handle_clicked(
gtk_widget_grab_focus(GTK_WIDGET(main_window)); // Focus on the window
koto_action_bar_toggle_reveal(action_bar, FALSE);
koto_action_bar_close(action_bar); // Close the action bar
koto_current_playlist_set_playlist(current_playlist, self->playlist, FALSE); // Set the current playlist to the artist's playlist but do not play immediately
koto_current_playlist_set_playlist(current_playlist, self->playlist, FALSE, FALSE); // Set the current playlist to the artist's playlist but do not play immediately
koto_playlist_set_track_as_current(self->playlist, track_uuid); // Set this track as the current one for the playlist
koto_playback_engine_set_track_by_uuid(playback_engine, track_uuid, FALSE); // Tell our playback engine to start playing at this track
}
@ -304,7 +304,7 @@ void koto_track_table_handle_track_name_clicked(
gtk_widget_add_css_class(GTK_WIDGET(self->track_title_button), "active");
koto_button_hide_image(self->track_num_button); // Go back to hiding the image
koto_track_table_set_playlist_model(self, KOTO_PREFERRED_MODEL_TYPE_SORT_BY_TRACK_NAME);
koto_track_table_set_playlist_model(self, KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_TRACK_NAME);
}
void koto_track_table_handle_track_num_clicked(
@ -320,13 +320,13 @@ void koto_track_table_handle_track_num_clicked(
(void) y;
KotoTrackTable * self = user_data;
KotoPreferredModelType current_model = koto_playlist_get_current_model(self->playlist);
KotoPreferredPlaylistSortType current_model = koto_playlist_get_current_model(self->playlist);
if (current_model == KOTO_PREFERRED_MODEL_TYPE_DEFAULT) { // Set to newest currently
koto_track_table_set_playlist_model(self, KOTO_PREFERRED_MODEL_TYPE_OLDEST_FIRST); // Sort reversed (oldest)
if (current_model == KOTO_PREFERRED_PLAYLIST_SORT_TYPE_DEFAULT) { // Set to newest currently
koto_track_table_set_playlist_model(self, KOTO_PREFERRED_PLAYLIST_SORT_TYPE_OLDEST_FIRST); // Sort reversed (oldest)
koto_button_show_image(self->track_num_button, TRUE); // Use inverted value (pan-up-symbolic)
} else {
koto_track_table_set_playlist_model(self, KOTO_PREFERRED_MODEL_TYPE_DEFAULT); // Sort newest
koto_track_table_set_playlist_model(self, KOTO_PREFERRED_PLAYLIST_SORT_TYPE_DEFAULT); // Sort newest
koto_button_show_image(self->track_num_button, FALSE); // Use pan down default
}
}
@ -385,7 +385,7 @@ void koto_track_table_handle_tracks_selected(
void koto_track_table_set_model(
KotoTrackTable * self,
KotoPreferredModelType model
KotoPreferredPlaylistSortType model
) {
if (!KOTO_IS_TRACK_TABLE(self)) {
return;
@ -394,15 +394,15 @@ void koto_track_table_set_model(
koto_playlist_apply_model(self->playlist, model); // Apply our new model
self->model = G_LIST_MODEL(koto_playlist_get_store(self->playlist)); // Get the latest generated model / store and cast it as a GListModel
if (model != KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ALBUM) { // Not sorting by album currently
if (model != KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_ALBUM) { // Not sorting by album currently
gtk_widget_remove_css_class(GTK_WIDGET(self->track_album_button), "active");
}
if (model != KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ARTIST) { // Not sorting by artist currently
if (model != KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_ARTIST) { // Not sorting by artist currently
gtk_widget_remove_css_class(GTK_WIDGET(self->track_artist_button), "active");
}
if (model != KOTO_PREFERRED_MODEL_TYPE_SORT_BY_TRACK_NAME) { // Not sorting by track name
if (model != KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_TRACK_NAME) { // Not sorting by track name
gtk_widget_remove_css_class(GTK_WIDGET(self->track_title_button), "active");
}
@ -414,7 +414,7 @@ void koto_track_table_set_model(
void koto_track_table_set_playlist_model (
KotoTrackTable * self,
KotoPreferredModelType model
KotoPreferredPlaylistSortType model
) {
if (!KOTO_IS_TRACK_TABLE(self)) { // Not a track table
return;
@ -427,15 +427,15 @@ void koto_track_table_set_playlist_model (
koto_playlist_apply_model(self->playlist, model); // Apply our new model
self->model = G_LIST_MODEL(koto_playlist_get_store(self->playlist)); // Get the latest generated model / store and cast it as a GListModel
if (model != KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ALBUM) { // Not sorting by album currently
if (model != KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_ALBUM) { // Not sorting by album currently
gtk_widget_remove_css_class(GTK_WIDGET(self->track_album_button), "active");
}
if (model != KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ARTIST) { // Not sorting by artist currently
if (model != KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_ARTIST) { // Not sorting by artist currently
gtk_widget_remove_css_class(GTK_WIDGET(self->track_artist_button), "active");
}
if (model != KOTO_PREFERRED_MODEL_TYPE_SORT_BY_TRACK_NAME) { // Not sorting by track name
if (model != KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_TRACK_NAME) { // Not sorting by track name
gtk_widget_remove_css_class(GTK_WIDGET(self->track_title_button), "active");
}
@ -444,9 +444,9 @@ void koto_track_table_set_playlist_model (
gtk_list_view_set_model(GTK_LIST_VIEW(self->track_list_view), self->selection_model); // Set our multi selection model to our provided model
KotoPreferredModelType current_model = koto_playlist_get_current_model(self->playlist); // Get the current model
KotoPreferredPlaylistSortType current_model = koto_playlist_get_current_model(self->playlist); // Get the current model
if (current_model == KOTO_PREFERRED_MODEL_TYPE_OLDEST_FIRST) {
if (current_model == KOTO_PREFERRED_PLAYLIST_SORT_TYPE_OLDEST_FIRST) {
koto_button_show_image(self->track_num_button, TRUE); // Immediately use pan-up-symbolic
}
}
@ -464,7 +464,7 @@ void koto_track_table_set_playlist(
}
self->playlist = playlist;
koto_track_table_set_playlist_model(self, KOTO_PREFERRED_MODEL_TYPE_DEFAULT); // TODO: Enable this to be changed
koto_track_table_set_playlist_model(self, KOTO_PREFERRED_PLAYLIST_SORT_TYPE_DEFAULT); // TODO: Enable this to be changed
}
void koto_track_table_setup_track_item(

View file

@ -20,7 +20,7 @@
#include <glib-2.0/glib-object.h>
#include <gtk-4.0/gtk/gtk.h>
#include "../playlist/playlist.h"
#include "koto-action-bar.h"
#include "action-bar.h"
G_BEGIN_DECLS
@ -98,12 +98,12 @@ void koto_track_table_handle_tracks_selected(
void koto_track_table_set_model(
KotoTrackTable * self,
KotoPreferredModelType model
KotoPreferredPlaylistSortType model
);
void koto_track_table_set_playlist_model(
KotoTrackTable * self,
KotoPreferredModelType model
KotoPreferredPlaylistSortType model
);
void koto_track_table_set_playlist(

View file

@ -37,6 +37,11 @@ enum {
PROP_PLAYBACK_CONTINUE_ON_PLAYLIST,
PROP_PLAYBACK_LAST_USED_VOLUME,
PROP_PLAYBACK_MAINTAIN_SHUFFLE,
PROP_PREFERRED_ALBUM_SORT_TYPE,
PROP_UI_ALBUM_INFO_SHOW_DESCRIPTION,
PROP_UI_ALBUM_INFO_SHOW_GENRES,
PROP_UI_ALBUM_INFO_SHOW_NARRATOR,
PROP_UI_ALBUM_INFO_SHOW_YEAR,
PROP_UI_THEME_DESIRED,
PROP_UI_THEME_OVERRIDE,
N_PROPS,
@ -69,8 +74,17 @@ struct _KotoConfig {
gdouble playback_last_used_volume;
gboolean playback_maintain_shuffle;
/* Misc Prefs */
KotoPreferredAlbumSortType preferred_album_sort_type;
/* UI Settings */
gboolean ui_album_info_show_description;
gboolean ui_album_info_show_genres;
gboolean ui_album_info_show_narrator;
gboolean ui_album_info_show_year;
gchar * ui_theme_desired;
gboolean ui_theme_override;
};
@ -132,6 +146,46 @@ static void koto_config_class_init(KotoConfigClass * c) {
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
);
config_props[PROP_PREFERRED_ALBUM_SORT_TYPE] = g_param_spec_string(
"artist-preferred-album-sort-type",
"Preferred album sort type (chronological or alphabetical-only)",
"Preferred album sort type (chronological or alphabetical-only)",
"default",
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
);
config_props[PROP_UI_ALBUM_INFO_SHOW_DESCRIPTION] = g_param_spec_boolean(
"ui-album-info-show-description",
"Show Description in Album Info",
"Show Description in Album Info",
TRUE,
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
);
config_props[PROP_UI_ALBUM_INFO_SHOW_GENRES] = g_param_spec_boolean(
"ui-album-info-show-genres",
"Show Genres in Album Info",
"Show Genres in Album Info",
TRUE,
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
);
config_props[PROP_UI_ALBUM_INFO_SHOW_NARRATOR] = g_param_spec_boolean(
"ui-album-info-show-narrator",
"Show Narrator in Album Info",
"Show Narrator in Album Info",
TRUE,
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
);
config_props[PROP_UI_ALBUM_INFO_SHOW_YEAR] = g_param_spec_boolean(
"ui-album-info-show-year",
"Show Year in Album Info",
"Show Year in Album Info",
TRUE,
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
);
config_props[PROP_UI_THEME_DESIRED] = g_param_spec_string(
"ui-theme-desired",
"Desired Theme",
@ -181,6 +235,18 @@ static void koto_config_get_property(
case PROP_PLAYBACK_MAINTAIN_SHUFFLE:
g_value_set_boolean(val, self->playback_maintain_shuffle);
break;
case PROP_UI_ALBUM_INFO_SHOW_DESCRIPTION:
g_value_set_boolean(val, self->ui_album_info_show_description);
break;
case PROP_UI_ALBUM_INFO_SHOW_GENRES:
g_value_set_boolean(val, self->ui_album_info_show_genres);
break;
case PROP_UI_ALBUM_INFO_SHOW_NARRATOR:
g_value_set_boolean(val, self->ui_album_info_show_narrator);
break;
case PROP_UI_ALBUM_INFO_SHOW_YEAR:
g_value_set_boolean(val, self->ui_album_info_show_year);
break;
case PROP_UI_THEME_DESIRED:
g_value_set_string(val, g_strdup(self->ui_theme_desired));
break;
@ -211,6 +277,21 @@ static void koto_config_set_property(
case PROP_PLAYBACK_MAINTAIN_SHUFFLE:
self->playback_maintain_shuffle = g_value_get_boolean(val);
break;
case PROP_PREFERRED_ALBUM_SORT_TYPE:
self->preferred_album_sort_type = g_strcmp0(g_value_get_string(val), "alphabetical") ? KOTO_PREFERRED_ALBUM_ALWAYS_ALPHABETICAL : KOTO_PREFERRED_ALBUM_SORT_TYPE_DEFAULT;
break;
case PROP_UI_ALBUM_INFO_SHOW_DESCRIPTION:
self->ui_album_info_show_description = g_value_get_boolean(val);
break;
case PROP_UI_ALBUM_INFO_SHOW_GENRES:
self->ui_album_info_show_genres = g_value_get_boolean(val);
break;
case PROP_UI_ALBUM_INFO_SHOW_NARRATOR:
self->ui_album_info_show_narrator = g_value_get_boolean(val);
break;
case PROP_UI_ALBUM_INFO_SHOW_YEAR:
self->ui_album_info_show_year = g_value_get_boolean(val);
break;
case PROP_UI_THEME_DESIRED:
self->ui_theme_desired = g_strdup(g_value_get_string(val));
break;
@ -238,7 +319,7 @@ toml_table_t * koto_config_get_library(
gchar * lib_uuid = (uuid_datum.ok) ? (gchar*) uuid_datum.u.s : NULL;
if (koto_utils_is_string_valid(lib_uuid) && (g_strcmp0(library_uuid, lib_uuid) == 0)) { // Is a valid string and the libraries match
if (koto_utils_string_is_valid(lib_uuid) && (g_strcmp0(library_uuid, lib_uuid) == 0)) { // Is a valid string and the libraries match
return library;
}
}
@ -253,7 +334,7 @@ void koto_config_load(
KotoConfig * self,
gchar * path
) {
if (!koto_utils_is_string_valid(path)) { // Path is not valid
if (!koto_utils_string_is_valid(path)) { // Path is not valid
return;
}
@ -374,6 +455,30 @@ void koto_config_load(
toml_table_t * ui_section = toml_table_in(conf, "ui");
if (ui_section) { // Have UI section
toml_datum_t album_info_show_description = toml_bool_in(ui_section, "album-info-show-description");
if (album_info_show_description.ok && (album_info_show_description.u.b != self->ui_album_info_show_description)) { // Changed if we are disabling description
g_object_set(self, "ui-album-info-show-description", album_info_show_description.u.b, NULL);
}
toml_datum_t album_info_show_genres = toml_bool_in(ui_section, "album-info-show-genres");
if (album_info_show_genres.ok && (album_info_show_genres.u.b != self->ui_album_info_show_genres)) { // Changed if we are disabling description
g_object_set(self, "ui-album-info-show-genres", album_info_show_genres.u.b, NULL);
}
toml_datum_t album_info_show_narrator = toml_bool_in(ui_section, "album-info-show-narrator");
if (album_info_show_narrator.ok && (album_info_show_narrator.u.b != self->ui_album_info_show_narrator)) { // Changed if we are disabling description
g_object_set(self, "ui-album-info-show-narrator", album_info_show_description.u.b, NULL);
}
toml_datum_t album_info_show_year = toml_bool_in(ui_section, "album-info-show-year");
if (album_info_show_year.ok && (album_info_show_year.u.b != self->ui_album_info_show_year)) { // Changed if we are disabling description
g_object_set(self, "ui-album-info-show-year", album_info_show_year.u.b, NULL);
}
toml_datum_t name = toml_string_in(ui_section, "theme-desired");
if (name.ok && (g_strcmp0(name.u.s, self->ui_theme_desired) != 0)) { // Have a name specified and they are different
@ -472,6 +577,10 @@ void koto_config_monitor_handle_changed(
}
}
KotoPreferredAlbumSortType koto_config_get_preferred_album_sort_type(KotoConfig * self) {
return self->preferred_album_sort_type;
}
/**
* Refresh will handle any FS notify change on our Koto config file and call load
**/
@ -568,7 +677,7 @@ void koto_config_save(KotoConfig * self) {
}
gchar * key_name = g_strdup(current_section_keyname->data);
gchar * key_name_replaced = koto_utils_replace_string_all(key_name, g_strdup_printf("%s-", (gchar*) section_name), ""); // Remove SECTIONNAME-
gchar * key_name_replaced = koto_utils_string_replace_all(key_name, g_strdup_printf("%s-", (gchar*) section_name), ""); // Remove SECTIONNAME-
const gchar * line = g_strdup_printf("\t%s = %s", key_name_replaced, prop_val);

View file

@ -18,6 +18,7 @@
#pragma once
#include <glib-2.0/glib.h>
#include <glib-2.0/gio/gio.h>
#include "../indexer/misc-types.h"
G_BEGIN_DECLS
@ -45,6 +46,8 @@ void koto_config_monitor_handle_changed(
gpointer user_data
);
KotoPreferredAlbumSortType koto_config_get_preferred_album_sort_type(KotoConfig * self);
void koto_config_refresh(KotoConfig * self);
void koto_config_save(KotoConfig * self);

View file

@ -259,7 +259,7 @@ void koto_cartographer_add_album(
gchar * album_uuid = koto_album_get_uuid(album); // Get the album UUID
if (!koto_utils_is_string_valid(album_uuid) || koto_cartographer_has_album_by_uuid(self, album_uuid)) { // Have the album or invalid UUID
if (!koto_utils_string_is_valid(album_uuid) || koto_cartographer_has_album_by_uuid(self, album_uuid)) { // Have the album or invalid UUID
return;
}
@ -287,7 +287,7 @@ void koto_cartographer_add_artist(
gchar * artist_uuid = koto_artist_get_uuid(artist);
if (!koto_utils_is_string_valid(artist_uuid) || koto_cartographer_has_artist_by_uuid(self, artist_uuid)) { // Have the artist or invalid UUID
if (!koto_utils_string_is_valid(artist_uuid) || koto_cartographer_has_artist_by_uuid(self, artist_uuid)) { // Have the artist or invalid UUID
return;
}
@ -316,7 +316,7 @@ void koto_cartographer_add_library(
gchar * library_uuid = koto_library_get_uuid(library);
if (!koto_utils_is_string_valid(library_uuid) || koto_cartographer_has_library_by_uuid(self, library_uuid)) { // Have the library or invalid UUID
if (!koto_utils_string_is_valid(library_uuid) || koto_cartographer_has_library_by_uuid(self, library_uuid)) { // Have the library or invalid UUID
return;
}
@ -387,7 +387,7 @@ void koto_cartographer_add_track(
gchar * track_uuid = koto_track_get_uuid(track);
if (!koto_utils_is_string_valid(track_uuid) || koto_cartographer_has_track_by_uuid(self, track_uuid)) { // Have the track or invalid UUID
if (!koto_utils_string_is_valid(track_uuid) || koto_cartographer_has_track_by_uuid(self, track_uuid)) { // Have the track or invalid UUID
return;
}
@ -424,7 +424,7 @@ KotoArtist * koto_cartographer_get_artist_by_name(
return NULL;
}
if (!koto_utils_is_string_valid(artist_name)) { // Not a valid name
if (!koto_utils_string_is_valid(artist_name)) { // Not a valid name
return NULL;
}
@ -439,7 +439,7 @@ KotoArtist * koto_cartographer_get_artist_by_uuid(
return NULL;
}
if (!koto_utils_is_string_valid(artist_uuid)) {
if (!koto_utils_string_is_valid(artist_uuid)) {
return NULL;
}
@ -454,7 +454,7 @@ KotoLibrary * koto_cartographer_get_library_by_uuid(
return NULL;
}
if (!koto_utils_is_string_valid(library_uuid)) { // Not a valid string
if (!koto_utils_string_is_valid(library_uuid)) { // Not a valid string
return NULL;
}
@ -469,7 +469,7 @@ KotoLibrary * koto_cartographer_get_library_containing_path(
return NULL;
}
if (!koto_utils_is_string_valid(relative_path)) { // Not a valid string
if (!koto_utils_string_is_valid(relative_path)) { // Not a valid string
return NULL;
}
@ -503,7 +503,7 @@ GList * koto_cartographer_get_libraries_for_storage_uuid(
GList * libraries = NULL; // Initialize our list
if (!koto_utils_is_string_valid(storage_uuid)) { // Not a valid storage UUID string
if (!koto_utils_string_is_valid(storage_uuid)) { // Not a valid storage UUID string
return libraries;
}
@ -544,6 +544,10 @@ KotoTrack * koto_cartographer_get_track_by_uuid(
return NULL;
}
if (!koto_utils_string_is_valid(track_uuid)) {
return NULL;
}
return g_hash_table_lookup(self->tracks, track_uuid);
}
@ -555,7 +559,7 @@ KotoTrack * koto_cartographer_get_track_by_uniqueish_key(
return NULL;
}
if (!koto_utils_is_string_valid(key)) {
if (!koto_utils_string_is_valid(key)) {
return NULL;
}
@ -588,7 +592,7 @@ gboolean koto_cartographer_has_album_by_uuid(
return FALSE;
}
if (!koto_utils_is_string_valid(album_uuid)) { // Not a valid UUID
if (!koto_utils_string_is_valid(album_uuid)) { // Not a valid UUID
return FALSE;
}
@ -618,7 +622,7 @@ gboolean koto_cartographer_has_artist_by_uuid(
return FALSE;
}
if (!koto_utils_is_string_valid(artist_uuid)) { // Not a valid UUID
if (!koto_utils_string_is_valid(artist_uuid)) { // Not a valid UUID
return FALSE;
}
@ -649,7 +653,7 @@ gboolean koto_cartographer_has_library_by_uuid(
return FALSE;
}
if (!koto_utils_is_string_valid(library_uuid)) { // Not a valid UUID
if (!koto_utils_string_is_valid(library_uuid)) { // Not a valid UUID
return FALSE;
}
@ -679,7 +683,7 @@ gboolean koto_cartographer_has_playlist_by_uuid(
return FALSE;
}
if (!koto_utils_is_string_valid(playlist_uuid)) { // Not a valid UUID
if (!koto_utils_string_is_valid(playlist_uuid)) { // Not a valid UUID
return FALSE;
}
@ -709,7 +713,7 @@ gboolean koto_cartographer_has_track_by_uuid(
return FALSE;
}
if (!koto_utils_is_string_valid(track_uuid)) { // Not a valid UUID
if (!koto_utils_string_is_valid(track_uuid)) { // Not a valid UUID
return FALSE;
}
@ -739,7 +743,7 @@ void koto_cartographer_remove_album_by_uuid(
return;
}
if (!koto_utils_is_string_valid(album_uuid)) {
if (!koto_utils_string_is_valid(album_uuid)) {
return;
}
@ -792,7 +796,7 @@ void koto_cartographer_remove_artist_by_uuid(
return;
}
if (!koto_utils_is_string_valid(artist_uuid)) { // Artist UUID not valid
if (!koto_utils_string_is_valid(artist_uuid)) { // Artist UUID not valid
return;
}
@ -842,7 +846,7 @@ void koto_cartographer_remove_playlist_by_uuid(
return;
}
if (!koto_utils_is_string_valid(playlist_uuid)) { // Not a valid playlist UUID string
if (!koto_utils_string_is_valid(playlist_uuid)) { // Not a valid playlist UUID string
return;
}
@ -883,7 +887,7 @@ void koto_cartographer_remove_track_by_uuid(
return;
}
if (!koto_utils_is_string_valid(track_uuid)) {
if (!koto_utils_string_is_valid(track_uuid)) {
return;
}

View file

@ -39,13 +39,13 @@ void close_db() {
int create_db_tables() {
gchar * tables_creation_queries = "CREATE TABLE IF NOT EXISTS artists(id string UNIQUE PRIMARY KEY, name string, art_path string);"
"CREATE TABLE IF NOT EXISTS albums(id string UNIQUE PRIMARY KEY, artist_id string, name string, art_path string, genres string, FOREIGN KEY(artist_id) REFERENCES artists(id) ON DELETE CASCADE);"
"CREATE TABLE IF NOT EXISTS albums(id string UNIQUE PRIMARY KEY, artist_id string, name string, description string, narrator string, art_path string, genres string, year int, FOREIGN KEY(artist_id) REFERENCES artists(id) ON DELETE CASCADE);"
"CREATE TABLE IF NOT EXISTS tracks(id string UNIQUE PRIMARY KEY, artist_id string, album_id string, name string, disc int, position int, duration int, genres string, FOREIGN KEY(artist_id) REFERENCES artists(id) ON DELETE CASCADE);"
"CREATE TABLE IF NOT EXISTS libraries_albums(id string, album_id string, path string, PRIMARY KEY (id, album_id) FOREIGN KEY(album_id) REFERENCES albums(id) ON DELETE CASCADE);"
"CREATE TABLE IF NOT EXISTS libraries_artists(id string, artist_id string, path string, PRIMARY KEY(id, artist_id) FOREIGN KEY(artist_id) REFERENCES artists(id) ON DELETE CASCADE);"
"CREATE TABLE IF NOT EXISTS libraries_tracks(id string, track_id string, path string, PRIMARY KEY(id, track_id) FOREIGN KEY(track_id) REFERENCES tracks(id) ON DELETE CASCADE);"
"CREATE TABLE IF NOT EXISTS playlist_meta(id string UNIQUE PRIMARY KEY, name string, art_path string, preferred_model int);"
"CREATE TABLE IF NOT EXISTS playlist_tracks(position INTEGER PRIMARY KEY AUTOINCREMENT, playlist_id string, track_id string, current int, FOREIGN KEY(playlist_id) REFERENCES playlist_meta(id), FOREIGN KEY(track_id) REFERENCES tracks(id) ON DELETE CASCADE);";
"CREATE TABLE IF NOT EXISTS playlist_meta(id string UNIQUE PRIMARY KEY, name string, art_path string, preferred_model int, album_id string, track_id string, playback_position_of_track int);"
"CREATE TABLE IF NOT EXISTS playlist_tracks(position INTEGER PRIMARY KEY AUTOINCREMENT, playlist_id string, track_id string, FOREIGN KEY(playlist_id) REFERENCES playlist_meta(id), FOREIGN KEY(track_id) REFERENCES tracks(id) ON DELETE CASCADE);";
return (new_transaction(tables_creation_queries, "Failed to create required tables", TRUE) == SQLITE_OK) ? KOTO_DB_SUCCESS : KOTO_DB_FAIL;
}

View file

@ -18,6 +18,7 @@
#include "cartographer.h"
#include "db.h"
#include "loaders.h"
#include "../indexer/album-playlist-funcs.h"
#include "../indexer/structs.h"
#include "../koto-utils.h"
@ -34,8 +35,8 @@ int process_artists(
(void) num_columns;
(void) column_names; // Don't need any of the params
gchar * artist_uuid = g_strdup(koto_utils_unquote_string(fields[0])); // First column is UUID
gchar * artist_name = g_strdup(koto_utils_unquote_string(fields[1])); // Second column is artist name
gchar * artist_uuid = g_strdup(koto_utils_string_unquote(fields[0])); // First column is UUID
gchar * artist_name = g_strdup(koto_utils_string_unquote(fields[1])); // Second column is artist name
KotoArtist * artist = koto_artist_new_with_uuid(artist_uuid); // Create our artist with the UUID
@ -63,7 +64,7 @@ int process_artists(
koto_artist_set_as_finalized(artist); // Indicate it is finalized
int tracks_rc = sqlite3_exec(koto_db, g_strdup_printf("SELECT * FROM tracks WHERE artist_id=\"%s\"", artist_uuid), process_tracks, NULL, NULL); // Process our tracks by artist uuid
int tracks_rc = sqlite3_exec(koto_db, g_strdup_printf("SELECT * FROM tracks WHERE artist_id=\"%s\" AND album_id=''", artist_uuid), process_tracks, NULL, NULL); // Load all tracks for an artist that are NOT in an album (e.g. artists without albums)
if (tracks_rc != SQLITE_OK) { // Failed to get our tracks
g_critical("Failed to read our tracks: %s", sqlite3_errmsg(koto_db));
@ -87,8 +88,8 @@ int process_artist_paths(
KotoArtist * artist = (KotoArtist*) data;
gchar * library_uuid = g_strdup(koto_utils_unquote_string(fields[0]));
gchar * relative_path = g_strdup(koto_utils_unquote_string(fields[2]));
gchar * library_uuid = g_strdup(koto_utils_string_unquote(fields[0]));
gchar * relative_path = g_strdup(koto_utils_string_unquote(fields[2]));
KotoLibrary * lib = koto_cartographer_get_library_by_uuid(koto_maps, library_uuid); // Get the library for this artist
@ -112,11 +113,14 @@ int process_albums(
KotoArtist * artist = (KotoArtist*) data;
gchar * album_uuid = g_strdup(koto_utils_unquote_string(fields[0]));
gchar * artist_uuid = g_strdup(koto_utils_unquote_string(fields[1]));
gchar * album_name = g_strdup(koto_utils_unquote_string(fields[2]));
gchar * album_art = (fields[3] != NULL) ? g_strdup(koto_utils_unquote_string(fields[3])) : NULL;
gchar * album_genres = (fields[4] != NULL) ? g_strdup(koto_utils_unquote_string(fields[4])) : NULL;
gchar * album_uuid = g_strdup(koto_utils_string_unquote(fields[0]));
gchar * artist_uuid = g_strdup(koto_utils_string_unquote(fields[1]));
gchar * album_name = g_strdup(koto_utils_string_unquote(fields[2]));
gchar * album_description = (fields[3] != NULL) ? g_strdup(koto_utils_string_unquote(fields[3])) : NULL;
gchar * album_narrator = (fields[4] != NULL) ? g_strdup(koto_utils_string_unquote(fields[4])) : NULL;
gchar * album_art = (fields[5] != NULL) ? g_strdup(koto_utils_string_unquote(fields[5])) : NULL;
gchar * album_genres = (fields[6] != NULL) ? g_strdup(koto_utils_string_unquote(fields[6])) : NULL;
guint64 * album_year = (guint64*) g_ascii_strtoull(fields[7], NULL, 10);
KotoAlbum * album = koto_album_new_with_uuid(artist, album_uuid); // Create our album
@ -124,16 +128,31 @@ int process_albums(
album,
"name",
album_name, // Set name
"description",
album_description,
"narrator",
album_narrator,
"art-path",
album_art, // Set art path if any
"preparsed-genres",
album_genres,
"year",
album_year,
NULL
);
koto_cartographer_add_album(koto_maps, album); // Add the album to our global cartographer
koto_artist_add_album(artist, album); // Add the album
int tracks_rc = sqlite3_exec(koto_db, g_strdup_printf("SELECT * FROM tracks WHERE album_id=\"%s\"", album_uuid), process_tracks, NULL, NULL); // Process all the tracks for this specific album
if (tracks_rc != SQLITE_OK) { // Failed to get our tracks
g_critical("Failed to read our tracks: %s", sqlite3_errmsg(koto_db));
return 1;
}
koto_album_mark_as_finalized(album); // Mark the album as finalized now that all tracks have been loaded, allowing our internal album playlist to re-sort itself
g_free(album_uuid);
g_free(artist_uuid);
g_free(album_name);
@ -156,26 +175,69 @@ int process_playlists(
(void) num_columns;
(void) column_names; // Don't need any of the params
gchar * playlist_uuid = g_strdup(koto_utils_unquote_string(fields[0])); // First column is UUID
gchar * playlist_name = g_strdup(koto_utils_unquote_string(fields[1])); // Second column is playlist name
gchar * playlist_art_path = g_strdup(koto_utils_unquote_string(fields[2])); // Third column is any art path
gchar * playlist_uuid = g_strdup(koto_utils_string_unquote(fields[0])); // First column is UUID
gchar * playlist_name = g_strdup(koto_utils_string_unquote(fields[1])); // Second column is playlist name
gchar * playlist_art_path = g_strdup(koto_utils_string_unquote(fields[2])); // Third column is any art path
guint64 playlist_preferred_sort = g_ascii_strtoull(koto_utils_string_unquote(fields[3]), NULL, 10); // Fourth column is preferred model which is an int
gchar * playlist_album_id = g_strdup(koto_utils_string_unquote(fields[4])); // Fifth column is any album ID
gchar * playlist_current_track_id = g_strdup(koto_utils_string_unquote(fields[5])); // Sixth column is any track ID
guint64 playlist_track_current_playback_pos = g_ascii_strtoull(koto_utils_string_unquote(fields[6]), NULL, 10); // Seventh column is any playback position for the track
KotoPlaylist * playlist = koto_playlist_new_with_uuid(playlist_uuid); // Create a playlist using the existing UUID
KotoPreferredPlaylistSortType sort_type = (KotoPreferredPlaylistSortType) playlist_preferred_sort;
koto_playlist_set_name(playlist, playlist_name); // Add the playlist name
koto_playlist_set_artwork(playlist, playlist_art_path); // Add the playlist art path
KotoPlaylist * playlist = NULL;
koto_cartographer_add_playlist(koto_maps, playlist); // Add to cartographer
gboolean for_album = FALSE;
int playlist_tracks_rc = sqlite3_exec(koto_db, g_strdup_printf("SELECT * FROM playlist_tracks WHERE playlist_id=\"%s\" ORDER BY position ASC", playlist_uuid), process_playlists_tracks, playlist, NULL); // Process our playlist tracks
if (koto_utils_string_is_valid(playlist_album_id)) { // Album UUID is set
KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, playlist_album_id); // Get the album based on the album ID set for the playlist
if (playlist_tracks_rc != SQLITE_OK) { // Failed to get our playlist tracks
g_critical("Failed to read our playlist tracks: %s", sqlite3_errmsg(koto_db));
return 1;
if (KOTO_IS_ALBUM(album)) { // Is an album
playlist = koto_album_get_playlist(album); // Get the playlist
for_album = TRUE;
}
} else {
playlist = koto_playlist_new_with_uuid(playlist_uuid); // Create a playlist using the existing UUID
koto_playlist_apply_model(playlist, sort_type); // Set the model based on what is stored
koto_cartographer_add_playlist(koto_maps, playlist); // Add to cartographer
}
if (!KOTO_IS_PLAYLIST(playlist)) { // Not a playlist yet, in this scenario it is likely the album no longer exists
goto free;
}
g_object_set(
playlist,
"name",
playlist_name,
"art-path",
playlist_art_path,
"ephemeral",
for_album,
NULL
);
if (!for_album) { // Isn't for an album
int playlist_tracks_rc = sqlite3_exec(koto_db, g_strdup_printf("SELECT * FROM playlist_tracks WHERE playlist_id=\"%s\" ORDER BY position ASC", playlist_uuid), process_playlists_tracks, playlist, NULL); // Process our playlist tracks
if (playlist_tracks_rc != SQLITE_OK) { // Failed to get our playlist tracks
g_critical("Failed to read our playlist tracks: %s", sqlite3_errmsg(koto_db));
goto free;
}
}
if (koto_utils_string_is_valid(playlist_current_track_id)) { // If we have a track UUID (probably)
KotoTrack * track = koto_cartographer_get_track_by_uuid(koto_maps, playlist_current_track_id); // Get the track UUID
if (KOTO_IS_TRACK(track)) { // If this is a track
koto_track_set_playback_position(track, playlist_track_current_playback_pos); // Set the playback position of the track
koto_playlist_set_track_as_current(playlist, playlist_current_track_id); // Ensure we have this track set as the current one in the playlist
}
}
koto_playlist_mark_as_finalized(playlist); // Mark as finalized since loading should be complete
free:
g_free(playlist_uuid);
g_free(playlist_name);
g_free(playlist_art_path);
@ -193,9 +255,8 @@ int process_playlists_tracks(
(void) num_columns;
(void) column_names; // Don't need these
gchar * playlist_uuid = g_strdup(koto_utils_unquote_string(fields[1]));
gchar * track_uuid = g_strdup(koto_utils_unquote_string(fields[2]));
gboolean current = g_strcmp0(koto_utils_unquote_string(fields[3]), "0");
gchar * playlist_uuid = g_strdup(koto_utils_string_unquote(fields[1]));
gchar * track_uuid = g_strdup(koto_utils_string_unquote(fields[2]));
KotoPlaylist * playlist = koto_cartographer_get_playlist_by_uuid(koto_maps, playlist_uuid); // Get the playlist
KotoTrack * track = koto_cartographer_get_track_by_uuid(koto_maps, track_uuid); // Get the track
@ -204,7 +265,7 @@ int process_playlists_tracks(
goto freeforret;
}
koto_playlist_add_track(playlist, track, current, FALSE); // Add the track to the playlist but don't re-commit to the table
koto_playlist_add_track(playlist, track, FALSE, FALSE); // Add the track to the playlist but don't re-commit to the table
freeforret:
g_free(playlist_uuid);
@ -223,14 +284,22 @@ int process_tracks(
(void) num_columns;
(void) column_names; // Don't need these
gchar * track_uuid = g_strdup(koto_utils_unquote_string(fields[0]));
gchar * artist_uuid = g_strdup(koto_utils_unquote_string(fields[1]));
gchar * album_uuid = g_strdup(koto_utils_unquote_string(fields[2]));
gchar * name = g_strdup(koto_utils_unquote_string(fields[3]));
gchar * track_uuid = g_strdup(koto_utils_string_unquote(fields[0]));
KotoTrack * existing_track = koto_cartographer_get_track_by_uuid(koto_maps, track_uuid);
if (KOTO_IS_TRACK(existing_track)) { // Already have track
g_free(track_uuid);
return 0;
}
gchar * artist_uuid = g_strdup(koto_utils_string_unquote(fields[1]));
gchar * album_uuid = g_strdup(koto_utils_string_unquote(fields[2]));
gchar * name = g_strdup(koto_utils_string_unquote(fields[3]));
guint * disc_num = (guint*) g_ascii_strtoull(fields[4], NULL, 10);
guint64 * position = (guint64*) g_ascii_strtoull(fields[5], NULL, 10);
guint64 * duration = (guint64*) g_ascii_strtoull(fields[6], NULL, 10);
gchar * genres = g_strdup(koto_utils_unquote_string(fields[7]));
gchar * genres = g_strdup(koto_utils_string_unquote(fields[7]));
KotoTrack * track = koto_track_new_with_uuid(track_uuid); // Create our file
@ -265,10 +334,12 @@ int process_tracks(
return 1;
}
koto_cartographer_add_track(koto_maps, track); // Add the track to cartographer if necessary
KotoArtist * artist = koto_cartographer_get_artist_by_uuid(koto_maps, artist_uuid); // Get the artist
koto_artist_add_track(artist, track); // Add the track for the artist
if (koto_utils_is_string_valid(album_uuid)) { // If we have an album UUID
if (koto_utils_string_is_valid(album_uuid)) { // If we have an album UUID
KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, album_uuid); // Attempt to get album
if (KOTO_IS_ALBUM(album)) { // This is an album
@ -293,13 +364,13 @@ int process_track_paths(
(void) num_columns;
(void) column_names; // Don't need these
KotoLibrary * library = koto_cartographer_get_library_by_uuid(koto_maps, koto_utils_unquote_string(fields[0]));
KotoLibrary * library = koto_cartographer_get_library_by_uuid(koto_maps, koto_utils_string_unquote(fields[0]));
if (!KOTO_IS_LIBRARY(library)) { // Not a library
return 1;
}
koto_track_set_path(track, library, koto_utils_unquote_string(fields[1]));
koto_track_set_path(track, library, koto_utils_string_unquote(fields[1]));
return 0;
}

View file

@ -20,6 +20,6 @@
G_BEGIN_DECLS
KotoPlaylist * koto_album_create_playlist(KotoAlbum * self);
KotoPlaylist * koto_album_get_playlist(KotoAlbum * self);
G_END_DECLS

View file

@ -36,10 +36,13 @@ enum {
PROP_0,
PROP_UUID,
PROP_DO_INITIAL_INDEX,
PROP_ALBUM_NAME,
PROP_NAME,
PROP_ART_PATH,
PROP_ARTIST_UUID,
PROP_ALBUM_PREPARED_GENRES,
PROP_DESCRIPTION,
PROP_NARRATOR,
PROP_YEAR,
N_PROPERTIES
};
@ -62,16 +65,20 @@ struct _KotoAlbum {
gchar * uuid;
gchar * name;
guint64 year;
gchar * description;
gchar * narrator;
gchar * art_path;
gchar * artist_uuid;
GList * genres;
GList * tracks;
KotoPlaylist * playlist;
GHashTable * paths;
gboolean has_album_art;
gboolean do_initial_index;
gboolean finalized;
};
struct _KotoAlbumClass {
@ -126,7 +133,7 @@ static void koto_album_class_init(KotoAlbumClass * c) {
G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
);
props[PROP_ALBUM_NAME] = g_param_spec_string(
props[PROP_NAME] = g_param_spec_string(
"name",
"Name",
"Name of Album",
@ -158,6 +165,32 @@ static void koto_album_class_init(KotoAlbumClass * c) {
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_WRITABLE
);
props[PROP_DESCRIPTION] = g_param_spec_string(
"description",
"Description of Album, typically for an audiobook or podcast",
"Description of Album, typically for an audiobook or podcast",
NULL,
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_WRITABLE
);
props[PROP_NARRATOR] = g_param_spec_string(
"narrator",
"Narrator of audiobook",
"Narrator of audiobook",
NULL,
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_WRITABLE
);
props[PROP_YEAR] = g_param_spec_uint64(
"year",
"Year",
"Year of release",
0,
G_MAXSHORT,
0,
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_WRITABLE
);
g_object_class_install_properties(gobject_class, N_PROPERTIES, props);
album_signals[SIGNAL_TRACK_ADDED] = g_signal_new(
@ -188,10 +221,19 @@ static void koto_album_class_init(KotoAlbumClass * c) {
}
static void koto_album_init(KotoAlbum * self) {
self->has_album_art = FALSE;
self->description = NULL;
self->genres = NULL;
self->tracks = NULL;
self->has_album_art = FALSE;
self->narrator = NULL;
self->paths = g_hash_table_new(g_str_hash, g_str_equal);
self->playlist = koto_playlist_new_with_uuid(NULL); // Create a playlist, with UUID set to NULL temporarily (until we know the album UUID)
koto_playlist_apply_model(self->playlist, KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_TRACK_POS); // Sort by track position
koto_playlist_set_album_uuid(self->playlist, self->uuid); // Set the playlist UUID to be the same as the album
g_object_set(self->playlist, "ephemeral", TRUE, NULL); // Set as ephemeral / temporary
koto_cartographer_add_playlist(koto_maps, self->playlist); // Add to cartographer
self->year = 0;
}
void koto_album_add_track(
@ -206,14 +248,11 @@ void koto_album_add_track(
return;
}
gchar * track_uuid = koto_track_get_uuid(track);
if (g_list_index(self->tracks, track_uuid) != -1) { // Have added it already
if (koto_playlist_get_position_of_track(self->playlist, track) != -1) { // Have added it already
return;
}
koto_cartographer_add_track(koto_maps, track); // Add the track to cartographer if necessary
self->tracks = g_list_insert_sorted_with_data(self->tracks, track_uuid, koto_track_helpers_sort_tracks_by_uuid, NULL);
GList * track_genres = koto_track_get_genres(track); // Get the genres for the track
GList * current_genre_list;
@ -234,6 +273,25 @@ void koto_album_add_track(
self->genres = g_list_append(self->genres, g_strdup(track_genre)); // Duplicate the genre and add it to our list
}
if (self->year == 0) { // Don't have a year set yet
guint64 track_year = koto_track_get_year(track); // Get the year from this track
if (track_year > 0) { // Have a probably valid year set
self->year = track_year; // Set our track
}
}
if (!koto_utils_string_is_valid(self->narrator)) { // No narrator set yet
gchar * track_narrator = koto_track_get_narrator(track); // Get the narrator for the track
if (koto_utils_string_is_valid(track_narrator)) { // If this track has a narrator
self->narrator = g_strdup(track_narrator);
g_free(track_narrator);
}
}
koto_playlist_add_track(self->playlist, track, FALSE, FALSE); // Add the track to our internal playlist
g_signal_emit(
self,
album_signals[SIGNAL_TRACK_ADDED],
@ -250,14 +308,17 @@ void koto_album_commit(KotoAlbum * self) {
gchar * genres_string = koto_utils_join_string_list(self->genres, ";");
gchar * commit_op = g_strdup_printf(
"INSERT INTO albums(id, artist_id, name, art_path, genres)"
"VALUES('%s', '%s', quote(\"%s\"), quote(\"%s\"), '%s')"
"ON CONFLICT(id) DO UPDATE SET artist_id=excluded.artist_id, name=excluded.name, art_path=excluded.art_path, genres=excluded.genres;",
"INSERT INTO albums(id, artist_id, name, description, narrator, art_path, genres, year)"
"VALUES('%s', '%s', quote(\"%s\"), quote(\"%s\"), quote(\"%s\"), quote(\"%s\"), '%s', %ld)"
"ON CONFLICT(id) DO UPDATE SET artist_id=excluded.artist_id, name=excluded.name, description=excluded.description, narrator=excluded.narrator, art_path=excluded.art_path, genres=excluded.genres, year=excluded.year;",
self->uuid,
self->artist_uuid,
self->name,
self->art_path,
genres_string
koto_utils_string_get_valid(self->name),
koto_utils_string_get_valid(self->description),
koto_utils_string_get_valid(self->narrator),
koto_utils_string_get_valid(self->art_path),
koto_utils_string_get_valid(genres_string),
self->year
);
new_transaction(commit_op, "Failed to write our album to the database", FALSE);
@ -284,42 +345,6 @@ void koto_album_commit(KotoAlbum * self) {
}
}
KotoPlaylist * koto_album_create_playlist(KotoAlbum * self) {
if (!KOTO_IS_ALBUM(self)) { // Not an album
return NULL;
}
if (self->tracks == NULL) { // No files to add to the playlist
return NULL;
}
KotoPlaylist * new_album_playlist = koto_playlist_new(); // Create a new playlist
g_object_set(new_album_playlist, "ephemeral", TRUE, NULL); // Set as ephemeral / temporary
// The following section effectively reverses our tracks, so the first is now last.
// It then adds them in reverse order, since our playlist add function will prepend to our queue
// This enables the preservation and defaulting of "newest" first everywhere else, while in albums preserving the rightful order of the album
// e.g. first track (0) being added last is actually first in the playlist's tracks
GList * reversed_tracks = g_list_copy(self->tracks); // Copy our tracks so we can reverse the order
reversed_tracks = g_list_reverse(reversed_tracks); // Actually reverse it
GList * t;
for (t = reversed_tracks; t != NULL; t = t->next) { // For each of the tracks
gchar * track_uuid = t->data;
koto_playlist_add_track_by_uuid(new_album_playlist, track_uuid, FALSE, FALSE); // Add the UUID, skip commit to table since it is temporary
}
g_list_free(t);
g_list_free(reversed_tracks);
koto_playlist_apply_model(new_album_playlist, KOTO_PREFERRED_MODEL_TYPE_DEFAULT); // Ensure we are using our default model
return new_album_playlist;
}
void koto_album_find_album_art(KotoAlbum * self) {
if (self->has_album_art) { // If we already have album art
return;
@ -376,14 +401,6 @@ void koto_album_find_album_art(KotoAlbum * self) {
closedir(dir);
}
GList * koto_album_get_genres(KotoAlbum * self) {
if (!KOTO_IS_ALBUM(self)) { // Not an album
return NULL;
}
return self->genres;
}
static void koto_album_get_property(
GObject * obj,
guint prop_id,
@ -399,7 +416,7 @@ static void koto_album_get_property(
case PROP_DO_INITIAL_INDEX:
g_value_set_boolean(val, self->do_initial_index);
break;
case PROP_ALBUM_NAME:
case PROP_NAME:
g_value_set_string(val, self->name);
break;
case PROP_ART_PATH:
@ -424,13 +441,12 @@ static void koto_album_set_property(
switch (prop_id) {
case PROP_UUID:
self->uuid = g_strdup(g_value_get_string(val));
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_UUID]);
koto_album_set_uuid(self, g_value_get_string(val));
break;
case PROP_DO_INITIAL_INDEX:
self->do_initial_index = g_value_get_boolean(val);
break;
case PROP_ALBUM_NAME: // Name of album
case PROP_NAME: // Name of album
koto_album_set_album_name(self, g_value_get_string(val));
break;
case PROP_ART_PATH: // Path to art
@ -442,6 +458,15 @@ static void koto_album_set_property(
case PROP_ALBUM_PREPARED_GENRES:
koto_album_set_preparsed_genres(self, g_strdup(g_value_get_string(val)));
break;
case PROP_DESCRIPTION:
koto_album_set_description(self, g_value_get_string(val));
break;
case PROP_NARRATOR:
koto_album_set_narrator(self, g_value_get_string(val));
break;
case PROP_YEAR:
koto_album_set_year(self, g_value_get_uint64(val));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
break;
@ -453,7 +478,27 @@ gchar * koto_album_get_art(KotoAlbum * self) {
return g_strdup("");
}
return g_strdup((self->has_album_art && koto_utils_is_string_valid(self->art_path)) ? self->art_path : "");
return g_strdup((self->has_album_art && koto_utils_string_is_valid(self->art_path)) ? self->art_path : "");
}
gchar * koto_album_get_artist_uuid(KotoAlbum * self) {
return KOTO_IS_ALBUM(self) ? self->artist_uuid : NULL;
}
gchar * koto_album_get_description(KotoAlbum * self) {
return KOTO_IS_ALBUM(self) ? self->description : NULL;
}
GList * koto_album_get_genres(KotoAlbum * self) {
return KOTO_IS_ALBUM(self) ? self->genres : NULL;
}
KotoLibraryType koto_album_get_lib_type(KotoAlbum * self) {
if (!KOTO_IS_ALBUM(self)) { // Not an album
return KOTO_LIBRARY_TYPE_UNKNOWN;
}
return koto_artist_get_lib_type(koto_cartographer_get_artist_by_uuid(koto_maps, self->artist_uuid)); // Get the lib type for the artist. If artist isn't valid, we just return UNKNOWN
}
gchar * koto_album_get_name(KotoAlbum * self) {
@ -461,23 +506,15 @@ gchar * koto_album_get_name(KotoAlbum * self) {
return NULL;
}
if (!koto_utils_is_string_valid(self->name)) { // Not set
if (!koto_utils_string_is_valid(self->name)) { // Not set
return NULL;
}
return g_strdup(self->name); // Return duplicate of the name
return self->name; // Return name of the album
}
gchar * koto_album_get_album_uuid(KotoAlbum * self) {
if (!KOTO_IS_ALBUM(self)) { // Not an album
return NULL;
}
if (!koto_utils_is_string_valid(self->uuid)) { // Not set
return NULL;
}
return g_strdup(self->uuid); // Return a duplicate of the UUID
gchar * koto_album_get_narrator(KotoAlbum * self) {
return KOTO_IS_ALBUM(self) ? self->narrator : NULL;
}
gchar * koto_album_get_path(KotoAlbum * self) {
@ -492,7 +529,7 @@ gchar * koto_album_get_path(KotoAlbum * self) {
KotoLibrary * cur_library = libs->data; // Get this as a KotoLibrary
gchar * library_relative_path = g_hash_table_lookup(self->paths, koto_library_get_uuid(cur_library)); // Get any relative path in our paths based on the current UUID
if (!koto_utils_is_string_valid(library_relative_path)) { // Not a valid path
if (!koto_utils_string_is_valid(library_relative_path)) { // Not a valid path
continue;
}
@ -502,12 +539,26 @@ gchar * koto_album_get_path(KotoAlbum * self) {
return NULL;
}
GList * koto_album_get_tracks(KotoAlbum * self) {
KotoPlaylist * koto_album_get_playlist(KotoAlbum * self) {
if (!KOTO_IS_ALBUM(self)) { // Not an album
return NULL;
}
return self->tracks; // Return the tracks
if (!KOTO_IS_PLAYLIST(self->playlist)) { // Not a playlist
return NULL;
}
return self->playlist;
}
GListStore * koto_album_get_store(KotoAlbum * self) {
KotoPlaylist * playlist = koto_album_get_playlist(self);
if (!KOTO_IS_PLAYLIST(playlist)) { // Not a playlist
return NULL;
}
return koto_playlist_get_store(playlist); // Return the store from the playlist
}
gchar * koto_album_get_uuid(KotoAlbum * self) {
@ -518,6 +569,89 @@ gchar * koto_album_get_uuid(KotoAlbum * self) {
return self->uuid; // Return the UUID
}
guint64 koto_album_get_year(KotoAlbum * self) {
return KOTO_IS_ALBUM(self) ? self->year : 0;
}
void koto_album_mark_as_finalized(KotoAlbum * self) {
if (!KOTO_IS_ALBUM(self)) { // Not an album
return;
}
if (self->finalized) { // Already finalized
return;
}
self->finalized = TRUE;
koto_playlist_mark_as_finalized(self->playlist);
//koto_playlist_apply_model(self->playlist, self->model); // Resort our playlist
}
void koto_album_remove_track(
KotoAlbum * self,
KotoTrack * track
) {
if (!KOTO_IS_ALBUM(self)) { // Not an album
return;
}
if (!KOTO_IS_TRACK(track)) { // Not a track
return;
}
koto_playlist_remove_track_by_uuid(
self->playlist,
koto_track_get_uuid(track)
);
g_signal_emit(
self,
album_signals[SIGNAL_TRACK_REMOVED],
0,
track
);
}
void koto_album_set_album_name(
KotoAlbum * self,
const gchar * album_name
) {
if (!KOTO_IS_ALBUM(self)) { // Not an album
return;
}
if (album_name == NULL) { // Not valid album name
return;
}
if (self->name != NULL) {
g_free(self->name);
}
self->name = g_strdup(album_name);
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_NAME]);
}
void koto_album_set_artist_uuid(
KotoAlbum * self,
const gchar * artist_uuid
) {
if (!KOTO_IS_ALBUM(self)) { // Not an album
return;
}
if (artist_uuid == NULL) {
return;
}
if (self->artist_uuid != NULL) {
g_free(self->artist_uuid);
}
self->artist_uuid = g_strdup(artist_uuid);
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_ARTIST_UUID]);
}
void koto_album_set_album_art(
KotoAlbum * self,
const gchar * album_art
@ -537,6 +671,56 @@ void koto_album_set_album_art(
self->art_path = g_strdup(album_art);
self->has_album_art = TRUE;
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_ART_PATH]);
}
void koto_album_set_as_current_playlist(KotoAlbum * self) {
if (!KOTO_IS_ALBUM(self)) {
return;
}
if (!KOTO_IS_CURRENT_PLAYLIST(current_playlist)) {
return;
}
if (!KOTO_IS_PLAYLIST(self->playlist)) { // Don't have a playlist for the album for some reason
return;
}
koto_current_playlist_set_playlist(current_playlist, self->playlist, TRUE, FALSE); // Set our new current playlist and start playing immediately
}
void koto_album_set_description(
KotoAlbum * self,
const gchar * description
) {
if (!KOTO_IS_ALBUM(self)) { // Not an album
return;
}
if (!koto_utils_string_is_valid(description)) {
return;
}
self->description = g_strdup(description);
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_DESCRIPTION]);
}
void koto_album_set_narrator(
KotoAlbum * self,
const gchar * narrator
) {
if (!KOTO_IS_ALBUM(self)) { // Not an album
return;
}
if (!koto_utils_string_is_valid(narrator)) {
return;
}
self->narrator = g_strdup(narrator);
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_NARRATOR]);
}
void koto_album_set_path(
@ -564,83 +748,6 @@ void koto_album_set_path(
self->do_initial_index = FALSE;
}
void koto_album_remove_track(
KotoAlbum * self,
KotoTrack * track
) {
if (!KOTO_IS_ALBUM(self)) { // Not an album
return;
}
if (!KOTO_IS_TRACK(track)) { // Not a track
return;
}
self->tracks = g_list_remove(self->tracks, koto_track_get_uuid(track));
g_signal_emit(
self,
album_signals[SIGNAL_TRACK_REMOVED],
0,
track
);
}
void koto_album_set_album_name(
KotoAlbum * self,
const gchar * album_name
) {
if (!KOTO_IS_ALBUM(self)) { // Not an album
return;
}
if (album_name == NULL) { // Not valid album name
return;
}
if (self->name != NULL) {
g_free(self->name);
}
self->name = g_strdup(album_name);
}
void koto_album_set_artist_uuid(
KotoAlbum * self,
const gchar * artist_uuid
) {
if (!KOTO_IS_ALBUM(self)) { // Not an album
return;
}
if (artist_uuid == NULL) {
return;
}
if (self->artist_uuid != NULL) {
g_free(self->artist_uuid);
}
self->artist_uuid = g_strdup(artist_uuid);
}
void koto_album_set_as_current_playlist(KotoAlbum * self) {
if (!KOTO_IS_ALBUM(self)) {
return;
}
if (!KOTO_IS_CURRENT_PLAYLIST(current_playlist)) {
return;
}
KotoPlaylist * album_playlist = koto_album_create_playlist(self);
if (!KOTO_IS_PLAYLIST(album_playlist)) {
return;
}
koto_current_playlist_set_playlist(current_playlist, album_playlist, TRUE); // Set our new current playlist and start playing immediately
}
void koto_album_set_preparsed_genres(
KotoAlbum * self,
gchar * genrelist
@ -649,7 +756,7 @@ void koto_album_set_preparsed_genres(
return;
}
if (!koto_utils_is_string_valid(genrelist)) { // If it is an empty string
if (!koto_utils_string_is_valid(genrelist)) { // If it is an empty string
return;
}
@ -665,8 +772,49 @@ void koto_album_set_preparsed_genres(
self->genres = preparsed_genres_list;
};
void koto_album_set_uuid(
KotoAlbum * self,
const gchar * uuid
) {
if (!KOTO_IS_ALBUM(self)) { // Not an album
return;
}
if (!koto_utils_string_is_valid(uuid)) {
return;
}
self->uuid = g_strdup(uuid);
g_object_set(
self->playlist,
"album-uuid",
self->uuid,
"uuid",
self->uuid, // Ensure the playlist has the same UUID as the album
NULL
);
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_UUID]);
}
void koto_album_set_year(
KotoAlbum * self,
guint64 year
) {
if (!KOTO_IS_ALBUM(self)) { // Not an album
return;
}
if (year <= 0) {
return;
}
self->year = year;
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_YEAR]);
}
KotoAlbum * koto_album_new(gchar * artist_uuid) {
if (!koto_utils_is_string_valid(artist_uuid)) { // Invalid artist UUID provided
if (!koto_utils_string_is_valid(artist_uuid)) { // Invalid artist UUID provided
return NULL;
}
@ -688,14 +836,12 @@ KotoAlbum * koto_album_new_with_uuid(
KotoArtist * artist,
const gchar * uuid
) {
gchar * artist_uuid = NULL;
g_object_get(artist, "uuid", &artist_uuid, NULL);
gchar * artist_uuid = koto_artist_get_uuid(artist);
return g_object_new(
KOTO_TYPE_ALBUM,
"artist-uuid",
artist,
artist_uuid,
"uuid",
g_strdup(uuid),
"do-initial-index",

View file

@ -17,6 +17,7 @@
#include <glib-2.0/glib.h>
#include <sqlite3.h>
#include "../config/config.h"
#include "../db/db.h"
#include "../db/cartographer.h"
#include "../playlist/playlist.h"
@ -26,6 +27,7 @@
#include "track-helpers.h"
extern KotoCartographer * koto_maps;
extern KotoConfig * config;
enum {
PROP_0,
@ -60,10 +62,12 @@ struct _KotoArtist {
gboolean finalized;
gboolean has_artist_art;
gchar * artist_name;
GList * albums;
GList * tracks;
GHashTable * paths;
KotoLibraryType type;
GQueue * albums;
GListStore * albums_store;
};
struct _KotoArtistClass {
@ -195,7 +199,8 @@ static void koto_artist_class_init(KotoArtistClass * c) {
}
static void koto_artist_init(KotoArtist * self) {
self->albums = NULL; // Create a new GList
self->albums = g_queue_new(); // Create a new GQueue
self->albums_store = g_list_store_new(KOTO_TYPE_ALBUM); // Create our GListStore of type KotoAlbum
self->content_playlist = koto_playlist_new(); // Create our playlist
g_object_set(
@ -223,7 +228,7 @@ void koto_artist_commit(KotoArtist * self) {
"VALUES ('%s', quote(\"%s\"), NULL)"
"ON CONFLICT(id) DO UPDATE SET name=excluded.name, art_path=excluded.art_path;",
self->uuid,
self->artist_name
koto_utils_string_get_valid(self->artist_name)
);
new_transaction(commit_op, "Failed to write our artist to the database", FALSE);
@ -248,14 +253,6 @@ void koto_artist_commit(KotoArtist * self) {
}
}
KotoPlaylist * koto_artist_get_playlist(KotoArtist * self) {
if (!KOTO_IS_ARTIST(self)) {
return NULL;
}
return self->content_playlist;
}
static void koto_artist_get_property(
GObject * obj,
guint prop_id,
@ -311,13 +308,21 @@ void koto_artist_add_album(
return;
}
gchar * album_uuid = koto_album_get_uuid(album);
GList * found_albums = g_queue_find(self->albums, album); // Try finding the album
if (g_list_index(self->albums, album_uuid) != -1) { // If we have already added the album
if (found_albums != NULL) { // Already has been added
g_list_free(found_albums);
return;
}
self->albums = g_list_append(self->albums, album_uuid); // Push to our album's UUID to the end of the list
g_list_free(found_albums);
g_queue_push_tail(self->albums, album); // Add the album to end of albums GQueue
g_list_store_append(self->albums_store, album); // Add the album to th estore as well
if (self->finalized) { // Is already finalized
koto_artist_apply_model(self, koto_config_get_preferred_album_sort_type(config)); // Apply our model to sort our albums gqueue and store
}
g_signal_emit(
self,
@ -358,12 +363,32 @@ void koto_artist_add_track(
);
}
GList * koto_artist_get_albums(KotoArtist * self) {
void koto_artist_apply_model(
KotoArtist * self,
KotoPreferredAlbumSortType model
) {
if (!KOTO_IS_ARTIST(self)) {
return;
}
g_queue_sort(self->albums, koto_artist_model_sort_albums, &model);
g_list_store_sort(self->albums_store, koto_artist_model_sort_albums, &model);
}
GQueue * koto_artist_get_albums(KotoArtist * self) {
if (!KOTO_IS_ARTIST(self)) { // Not an artist
return NULL;
}
return g_list_copy(self->albums);
return self->albums;
}
GListStore * koto_artist_get_albums_store(KotoArtist * self) {
if (!KOTO_IS_ARTIST(self)) { // Not an artist
return NULL;
}
return self->albums_store;
}
KotoAlbum * koto_artist_get_album_by_name(
@ -377,15 +402,15 @@ KotoAlbum * koto_artist_get_album_by_name(
KotoAlbum * album = NULL;
GList * cur_list_iter;
for (cur_list_iter = self->albums; cur_list_iter != NULL; cur_list_iter = cur_list_iter->next) { // Iterate through our albums by their UUIDs
KotoAlbum * album_for_uuid = koto_cartographer_get_album_by_uuid(koto_maps, cur_list_iter->data); // Get the album
for (cur_list_iter = self->albums->head; cur_list_iter != NULL; cur_list_iter = cur_list_iter->next) { // Iterate through our albums by their UUIDs
KotoAlbum * this_album = cur_list_iter->data;
if (!KOTO_IS_ALBUM(album_for_uuid)) { // Not an album
if (!KOTO_IS_ALBUM(this_album)) { // Not an album
continue;
}
if (g_strcmp0(koto_album_get_name(album_for_uuid), album_name) == 0) { // These album names match
album = album_for_uuid;
if (g_strcmp0(koto_album_get_name(this_album), album_name) == 0) { // These album names match
album = this_album;
break;
}
}
@ -398,7 +423,15 @@ gchar * koto_artist_get_name(KotoArtist * self) {
return g_strdup("");
}
return g_strdup(koto_utils_is_string_valid(self->artist_name) ? self->artist_name : ""); // Return artist name if set
return g_strdup(koto_utils_string_is_valid(self->artist_name) ? self->artist_name : ""); // Return artist name if set
}
KotoPlaylist * koto_artist_get_playlist(KotoArtist * self) {
if (!KOTO_IS_ARTIST(self)) {
return NULL;
}
return self->content_playlist;
}
GList * koto_artist_get_tracks(KotoArtist * self) {
@ -425,7 +458,12 @@ void koto_artist_remove_album(
return;
}
self->albums = g_list_remove(self->albums, koto_album_get_uuid(album));
g_queue_remove(self->albums, album); // Remove the album
guint position = 0;
if (g_list_store_find(self->albums_store, album, &position)) { // Found the album in the list store
g_list_store_remove(self->albums_store, position); // Remove from the list store
}
g_signal_emit(
self,
@ -468,11 +506,11 @@ void koto_artist_set_artist_name(
return;
}
if (!koto_utils_is_string_valid(artist_name)) { // No artist name
if (!koto_utils_string_is_valid(artist_name)) { // No artist name
return;
}
if (koto_utils_is_string_valid(self->artist_name)) { // Has artist name
if (koto_utils_string_is_valid(self->artist_name)) { // Has artist name
g_free(self->artist_name);
}
@ -487,7 +525,7 @@ void koto_artist_set_as_finalized(KotoArtist * self) {
self->finalized = TRUE;
if (g_list_length(self->albums) == 0) { // Have no albums
if (g_queue_get_length(self->albums) == 0) { // Have no albums
g_signal_emit_by_name(self, "has-no-albums");
}
}
@ -515,6 +553,36 @@ void koto_artist_set_path(
self->type = koto_library_get_lib_type(lib); // Define our artist type as the type from the library
}
gint koto_artist_model_sort_albums(
gconstpointer first_item,
gconstpointer second_item,
gpointer user_data
) {
KotoPreferredAlbumSortType * preferred_model = (KotoPreferredAlbumSortType*) user_data;
KotoAlbum * first_album = KOTO_ALBUM(first_item);
KotoAlbum * second_album = KOTO_ALBUM(second_item);
if (KOTO_IS_ALBUM(first_album) && !KOTO_IS_ALBUM(second_album)) { // First album is valid, second isn't
return -1;
} else if (!KOTO_IS_ALBUM(first_album) && KOTO_IS_ALBUM(second_album)) { // Second album is valid, first isn't
return 1;
}
if (preferred_model == KOTO_PREFERRED_ALBUM_SORT_TYPE_DEFAULT) { // Sort chronological before alphabetical
guint64 fa_year = koto_album_get_year(first_album);
guint64 sa_year = koto_album_get_year(second_album);
if (fa_year > sa_year) { // First album is newer than second
return -1;
} else if (fa_year < sa_year) { // Second album is newer than first
return 1;
}
}
return g_utf8_collate(koto_album_get_name(first_album), koto_album_get_name(second_album));
}
KotoArtist * koto_artist_new(gchar * artist_name) {
KotoArtist * artist = g_object_new(
KOTO_TYPE_ARTIST,

View file

@ -92,12 +92,12 @@ void index_folder(
gchar * artist_name = g_strdup(split[split_len - 3]);
g_strfreev(split);
if (!koto_utils_is_string_valid(album_name)) {
if (!koto_utils_string_is_valid(album_name)) {
g_free(album_name);
continue;
}
if (!koto_utils_is_string_valid(artist_name)) {
if (!koto_utils_string_is_valid(artist_name)) {
g_free(album_name);
g_free(artist_name);
continue;
@ -143,14 +143,17 @@ void index_file(
return;
}
gboolean for_audiobook = (koto_library_get_lib_type(lib) == KOTO_LIBRARY_TYPE_AUDIOBOOK);
gchar * relative_path_to_file = koto_library_get_relative_path_to_file(lib, g_strdup(path)); // Strip out library path so we have a relative path to the file
gchar * file_basename = g_path_get_basename(relative_path_to_file);
gchar ** split_on_relative_slashes = g_strsplit(relative_path_to_file, G_DIR_SEPARATOR_S, -1); // Split based on separator (e.g. / )
guint slash_sep_count = g_strv_length(split_on_relative_slashes);
gchar * artist_author_podcast_name = g_strdup(split_on_relative_slashes[0]); // No matter what, artist should be first
gchar * album_or_audiobook_name = NULL;
gchar * file_name = koto_track_helpers_get_name_for_file(path, artist_author_podcast_name); // Get the name of the file
guint cd = (guint) 1;
guint cd = (guint) 0;
if (slash_sep_count >= 3) { // If this it is at least "artist" + "album" + "file" (or with CD)
album_or_audiobook_name = g_strdup(split_on_relative_slashes[1]); // Duplicate the second item as the album or audiobook name
@ -159,22 +162,41 @@ void index_file(
// #region CD parsing logic
if ((slash_sep_count == 4)) { // If is at least "artist" + "album" + "cd" + "file"
gchar * cd_str = g_strdup(g_strstrip(koto_utils_replace_string_all(g_utf8_strdown(split_on_relative_slashes[2], -1), "cd", ""))); // Replace a lowercased version of our CD ("cd") and trim any whitespace
gchar * cd_str = g_strdup(g_strstrip(koto_utils_string_replace_all(g_utf8_strdown(split_on_relative_slashes[2], -1), "cd", ""))); // Replace a lowercased version of our CD ("cd") and trim any whitespace
cd = (guint) g_ascii_strtoull(cd_str, NULL, 10); // Attempt to convert
cd = (guint) g_ascii_strtoull(cd_str, NULL, 10); // Attempt to convert}
}
if (cd == 0) { // Had an error during conversion
cd = 1; // Set back to 1
}
if (for_audiobook && (cd == 0)) { // No CD yet and is for an audiobook
cd = koto_track_helpers_get_cd_based_on_file_name(file_basename); // Base on file name
} else {
cd = 1;
}
// #endregion
gchar * file_name = NULL;
if (for_audiobook) { // If this is for an audiobook library
guint64 pos = koto_track_helpers_get_position_based_on_file_name(file_basename);
if (cd != 1) { // On a non "Part 1" audiobook
file_name = g_strdup_printf("%s - Part %u - %lu", album_or_audiobook_name, cd, pos);
} else { // Part 1 / CD 1
file_name = g_strdup_printf("%s - %lu", album_or_audiobook_name, pos);
}
}
if (!koto_utils_string_is_valid(file_name)) { // No valid file name yet
file_name = koto_track_helpers_get_name_for_file(path, artist_author_podcast_name); // Get the name of the file
}
g_strfreev(split_on_relative_slashes);
g_free(file_basename);
gchar * sorta_uniqueish_key = NULL;
if (koto_utils_is_string_valid(album_or_audiobook_name)) { // Have audiobook or album name
if (koto_utils_string_is_valid(album_or_audiobook_name)) { // Have audiobook or album name
sorta_uniqueish_key = g_strdup_printf("%s-%s-%s", artist_author_podcast_name, album_or_audiobook_name, file_name);
} else { // No audiobook or album name
sorta_uniqueish_key = g_strdup_printf("%s-%s", artist_author_podcast_name, file_name);
@ -193,7 +215,7 @@ void index_file(
KotoAlbum * album = NULL;
if (koto_utils_is_string_valid(album_or_audiobook_name)) { // Have an album or audiobook name
if (koto_utils_string_is_valid(album_or_audiobook_name)) { // Have an album or audiobook name
KotoAlbum * possible_album = koto_artist_get_album_by_name(artist, album_or_audiobook_name);
album = KOTO_IS_ALBUM(possible_album) ? possible_album : NULL;
}

View file

@ -245,7 +245,7 @@ gchar * koto_library_get_relative_path_to_file(
}
gchar * appended_slash_to_library_path = g_str_has_suffix(self->path, G_DIR_SEPARATOR_S) ? g_strdup(self->path) : g_strdup_printf("%s%s", g_strdup(self->path), G_DIR_SEPARATOR_S);
gchar * cleaned_path = koto_utils_replace_string_all(full_path, appended_slash_to_library_path, ""); // Replace the full path
gchar * cleaned_path = koto_utils_string_replace_all(full_path, appended_slash_to_library_path, ""); // Replace the full path
g_free(appended_slash_to_library_path);
return cleaned_path;
@ -299,11 +299,11 @@ void koto_library_set_name(
return;
}
if (!koto_utils_is_string_valid(library_name)) { // Not a string
if (!koto_utils_string_is_valid(library_name)) { // Not a string
return;
}
if (koto_utils_is_string_valid(self->name)) { // Name already set
if (koto_utils_string_is_valid(self->name)) { // Name already set
g_free(self->name); // Free the existing value
}
@ -319,15 +319,15 @@ void koto_library_set_path(
return;
}
if (!koto_utils_is_string_valid(path)) { // Not a valid string
if (!koto_utils_string_is_valid(path)) { // Not a valid string
return;
}
if (koto_utils_is_string_valid(self->path)) {
if (koto_utils_string_is_valid(self->path)) {
g_free(self->path);
}
self->relative_path = g_path_is_absolute(path) ? koto_utils_replace_string_all(path, self->mount_path, "") : path; // Ensure path is relative to our mount, even if the mount is really our own system partition
self->relative_path = g_path_is_absolute(path) ? koto_utils_string_replace_all(path, self->mount_path, "") : path; // Ensure path is relative to our mount, even if the mount is really our own system partition
self->path = g_build_path(G_DIR_SEPARATOR_S, self->mount_path, self->relative_path, NULL); // Ensure our path is to whatever the current path of the mount + relative path is
}
@ -345,7 +345,7 @@ void koto_library_set_storage_uuid(
g_free(self->mount_path);
}
if (!koto_utils_is_string_valid(storage_uuid)) { // Not a valid string, which actually is allowed for built-ins
if (!koto_utils_string_is_valid(storage_uuid)) { // Not a valid string, which actually is allowed for built-ins
self->mount = NULL;
self->mount_path = g_strdup_printf("%s%s", g_get_home_dir(), G_DIR_SEPARATOR_S); // Set mount path to user's home directory
self->storage_uuid = NULL;
@ -378,11 +378,11 @@ gchar * koto_library_to_config_string(KotoLibrary * self) {
g_strv_builder_add(lib_builder, g_strdup_printf("\tdirectory=\"%s\"", self->relative_path)); // Add the directory
if (koto_utils_is_string_valid(self->name)) { // Have a library name
if (koto_utils_string_is_valid(self->name)) { // Have a library name
g_strv_builder_add(lib_builder, g_strdup_printf("\tname=\"%s\"", self->name)); // Add the name
}
if (koto_utils_is_string_valid(self->storage_uuid)) { // Have a storage UUID (not applicable to built-ins)
if (koto_utils_string_is_valid(self->storage_uuid)) { // Have a storage UUID (not applicable to built-ins)
g_strv_builder_add(lib_builder, g_strdup_printf("\tstorage_uuid=\"%s\"", self->storage_uuid)); // Add the storage UUID
}

22
src/indexer/misc-types.h Normal file
View file

@ -0,0 +1,22 @@
/* misc-types.h
*
* Copyright 2021 Joshua Strobl
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
typedef enum {
KOTO_PREFERRED_ALBUM_SORT_TYPE_DEFAULT, // Chronological is considered default
KOTO_PREFERRED_ALBUM_ALWAYS_ALPHABETICAL, // Prefer sorting alphabetically
} KotoPreferredAlbumSortType;

View file

@ -16,9 +16,11 @@
*/
#pragma once
#include <glib-2.0/glib-object.h>
#include <glib-2.0/glib.h>
#include <glib-2.0/gio/gio.h>
#include <magic.h>
#include <toml.h>
#include "misc-types.h"
typedef enum {
KOTO_LIBRARY_TYPE_AUDIOBOOK = 1,
@ -148,9 +150,16 @@ void koto_artist_add_track(
KotoTrack * track
);
void koto_artist_apply_model(
KotoArtist * self,
KotoPreferredAlbumSortType model
);
void koto_artist_commit(KotoArtist * self);
GList * koto_artist_get_albums(KotoArtist * self);
GQueue * koto_artist_get_albums(KotoArtist * self);
GListStore * koto_artist_get_albums_store(KotoArtist * self);
KotoAlbum * koto_artist_get_album_by_name(
KotoArtist * self,
@ -159,12 +168,20 @@ KotoAlbum * koto_artist_get_album_by_name(
gchar * koto_artist_get_name(KotoArtist * self);
gchar * koto_artist_get_path(KotoArtist * self);
GList * koto_artist_get_tracks(KotoArtist * self);
gchar * koto_artist_get_uuid(KotoArtist * self);
KotoLibraryType koto_artist_get_lib_type(KotoArtist * self);
gint koto_artist_model_sort_albums(
gconstpointer first_item,
gconstpointer second_item,
gpointer user_data
);
void koto_artist_remove_album(
KotoArtist * self,
KotoAlbum * album
@ -211,16 +228,28 @@ void koto_album_find_album_art(KotoAlbum * self);
gchar * koto_album_get_art(KotoAlbum * self);
gchar * koto_album_get_artist_uuid(KotoAlbum * self);
gchar * koto_album_get_description(KotoAlbum * self);
GList * koto_album_get_genres(KotoAlbum * self);
KotoLibraryType koto_album_get_lib_type(KotoAlbum * self);
gchar * koto_album_get_name(KotoAlbum * self);
gchar * koto_album_get_album_uuid(KotoAlbum * self);
gchar * koto_album_get_narrator(KotoAlbum * self);
gchar * koto_album_get_path(KotoAlbum * self);
GList * koto_album_get_tracks(KotoAlbum * self);
GListStore * koto_album_get_store(KotoAlbum * self);
gchar * koto_album_get_uuid(KotoAlbum * self);
guint64 koto_album_get_year(KotoAlbum * self);
void koto_album_mark_as_finalized(KotoAlbum * self);
void koto_album_remove_track(
KotoAlbum * self,
KotoTrack * track
@ -241,8 +270,17 @@ void koto_album_set_artist_uuid(
const gchar * artist_uuid
);
void koto_album_set_description(
KotoAlbum * self,
const char * description
);
void koto_album_set_as_current_playlist(KotoAlbum * self);
void koto_album_set_narrator(
KotoAlbum * self,
const char * narrator
);
void koto_album_set_path(
KotoAlbum * self,
@ -255,6 +293,16 @@ void koto_album_set_preparsed_genres(
gchar * genrelist
);
void koto_album_set_uuid(
KotoAlbum * self,
const gchar * uuid
);
void koto_album_set_year(
KotoAlbum * self,
guint64 year
);
/**
* File / Track Functions
**/
@ -270,6 +318,8 @@ KotoTrack * koto_track_new_with_uuid(const gchar * uuid);
void koto_track_commit(KotoTrack * self);
gchar * koto_track_get_description(KotoTrack * self);
guint koto_track_get_disc_number(KotoTrack * self);
guint64 koto_track_get_duration(KotoTrack * self);
@ -282,12 +332,18 @@ gchar * koto_track_get_path(KotoTrack * self);
gchar * koto_track_get_name(KotoTrack * self);
gchar * koto_track_get_narrator(KotoTrack * self);
guint64 koto_track_get_playback_position(KotoTrack * self);
guint64 koto_track_get_position(KotoTrack * self);
gchar * koto_track_get_uniqueish_key(KotoTrack * self);
gchar * koto_track_get_uuid(KotoTrack * self);
guint64 koto_track_get_year(KotoTrack * self);
void koto_track_remove_from_playlist(
KotoTrack * self,
gchar * playlist_uuid
@ -300,13 +356,7 @@ void koto_track_set_album_uuid(
void koto_track_save_to_playlist(
KotoTrack * self,
gchar * playlist_uuid,
gint current
);
void koto_track_set_file_name(
KotoTrack * self,
gchar * new_file_name
gchar * playlist_uuid
);
void koto_track_set_cd(
@ -314,16 +364,31 @@ void koto_track_set_cd(
guint cd
);
void koto_track_set_description(
KotoTrack * self,
const gchar * description
);
void koto_track_set_duration(
KotoTrack * self,
guint64 duration
);
void koto_track_set_file_name(
KotoTrack * self,
gchar * new_file_name
);
void koto_track_set_genres(
KotoTrack * self,
char * genrelist
);
void koto_track_set_narrator(
KotoTrack * self,
const gchar * narrator
);
void koto_track_set_parsed_name(
KotoTrack * self,
gchar * new_parsed_name
@ -335,6 +400,11 @@ void koto_track_set_path(
gchar * fixed_path
);
void koto_track_set_playback_position(
KotoTrack * self,
guint64 position
);
void koto_track_set_position(
KotoTrack * self,
guint64 pos
@ -345,6 +415,11 @@ void koto_track_set_preparsed_genres(
gchar * genrelist
);
void koto_track_set_year(
KotoTrack * self,
guint64 year
);
void koto_track_update_metadata(KotoTrack * self);
G_END_DECLS

View file

@ -17,8 +17,8 @@
#include <glib-2.0/glib.h>
#include <taglib/tag_c.h>
#include "../components/track-item.h"
#include "../db/cartographer.h"
#include "../koto-track-item.h"
#include "../koto-utils.h"
#include "structs.h"
@ -40,7 +40,40 @@ void koto_track_helpers_init() {
gchar * koto_track_helpers_get_corrected_genre(gchar * original_genre) {
gchar * lookedup_genre = g_hash_table_lookup(genre_replacements, original_genre); // Look up the genre
return koto_utils_is_string_valid(lookedup_genre) ? lookedup_genre : original_genre;
return koto_utils_string_is_valid(lookedup_genre) ? lookedup_genre : original_genre;
}
guint64 koto_track_helpers_get_cd_based_on_file_name(const gchar * file_name) {
gchar ** part_split = g_strsplit(file_name, "Part", -1);
gchar * part_str = NULL;
for (guint i = 0; i < g_strv_length(part_split); i++) { // Iterate on the parts
gchar * stripped_part = g_strdup(g_strstrip(part_split[i])); // Trim the whitespace around this part
gchar ** split = g_regex_split_simple("^([\\d]+)", stripped_part, G_REGEX_JAVASCRIPT_COMPAT, 0);
g_free(stripped_part); // Free the stripped part
if (g_strv_length(split) > 1) { // Has positional info at the beginning of the string
part_str = g_strdup(split[1]);
g_strfreev(split);
break;
} else {
g_strfreev(split);
}
}
guint64 cd = 0;
if (koto_utils_string_is_valid(part_str)) { // Have a valid string for the part
cd = g_ascii_strtoull(part_str, NULL, 10);
g_free(part_str);
}
if (cd == 0) {
cd = 1; // Should be first CD, not 0
}
return cd;
}
gchar * koto_track_helpers_get_name_for_file(
@ -58,19 +91,19 @@ gchar * koto_track_helpers_get_name_for_file(
taglib_tag_free_strings(); // Free strings
taglib_file_free(t_file); // Free the file
if (koto_utils_is_string_valid(file_name)) { // File name not set yet
if (koto_utils_string_is_valid(file_name)) { // File name not set yet
return file_name;
}
gchar * name_without_hyphen_surround = koto_utils_replace_string_all(koto_utils_get_filename_without_extension((gchar*) path), " - ", ""); // Remove - surrounded by spaces
gchar * name_without_hyphen_surround = koto_utils_string_replace_all(koto_utils_get_filename_without_extension((gchar*) path), " - ", ""); // Remove - surrounded by spaces
gchar * name_without_hyphen = koto_utils_replace_string_all(name_without_hyphen_surround, "-", ""); // Remove just -
gchar * name_without_hyphen = koto_utils_string_replace_all(name_without_hyphen_surround, "-", ""); // Remove just -
g_free(name_without_hyphen_surround);
file_name = koto_utils_replace_string_all(name_without_hyphen, "_", " "); // Replace underscore with whitespace
file_name = koto_utils_string_replace_all(name_without_hyphen, "_", " "); // Replace underscore with whitespace
if (koto_utils_is_string_valid(optional_artist_name)) { // Was provided an optional artist name
gchar * replaced_artist = koto_utils_replace_string_all(file_name, optional_artist_name, ""); // Remove the artist
if (koto_utils_string_is_valid(optional_artist_name)) { // Was provided an optional artist name
gchar * replaced_artist = koto_utils_string_replace_all(file_name, optional_artist_name, ""); // Remove the artist
g_free(file_name);
file_name = g_strdup(replaced_artist);
g_free(replaced_artist);
@ -87,21 +120,67 @@ gchar * koto_track_helpers_get_name_for_file(
}
guint64 koto_track_helpers_get_position_based_on_file_name(const gchar * file_name) {
guint64 position = 0; // Default to position 0
gchar ** split = g_regex_split_simple("^([\\d]+)", file_name, G_REGEX_JAVASCRIPT_COMPAT, 0);
GRegex * num_pat = g_regex_new("^([\\d]+)", G_REGEX_JAVASCRIPT_COMPAT, 0, NULL);
gchar ** split = g_regex_split(num_pat, file_name, 0);
if (g_strv_length(split) > 1) { // Has positional info at the beginning of the file
gchar * num = split[1];
gchar * num = g_strdup(split[1]);
g_strfreev(split);
if ((strcmp(num, "0") != 0) && (strcmp(num, "00") != 0)) { // Is not zero
guint64 potential_pos = g_ascii_strtoull(num, NULL, 10); // Attempt to convert
if (potential_pos != 0) { // Got a legitimate position
position = potential_pos; // Update position
g_free(num_pat); // Free our regex
return potential_pos; // Return this position
}
}
}
gchar * fn_no_ext = koto_utils_get_filename_without_extension((gchar*) file_name); // Get the filename without the extension
split = g_strsplit(fn_no_ext, ".", -1); // Split every time we see .
guint len_of_extension_split = g_strv_length(split);
gchar * fn_last_split_on_period = g_strdup(split[len_of_extension_split - 1]);
g_free(fn_no_ext);
g_strfreev(split); // Free our split
gchar ** whitespace_split = g_strsplit(fn_last_split_on_period, " ", -1); // Split on whitespace
g_free(fn_last_split_on_period);
gchar * last_item = koto_utils_string_replace_all(whitespace_split[g_strv_length(split) - 1], "#", ""); // Get last item, removing any # from it
g_strfreev(whitespace_split);
gchar ** hyphen_split = g_strsplit(last_item, "-", -1); // Split on hyphen
g_free(last_item);
gchar * position_str = NULL;
for (guint i = 0; i < g_strv_length(hyphen_split); i++) { // Iterate over each item
gchar * pos_str = hyphen_split[i];
if (!g_regex_match(num_pat, pos_str, 0, NULL)) { // Is not a number
continue;
}
position_str = g_strdup(pos_str);
break;
}
g_strfreev(hyphen_split);
guint64 position = 0;
if (position_str != NULL) { // If we have a string defined
if (g_regex_match(num_pat, position_str, 0, NULL)) { // Matches being a number
position = g_ascii_strtoull(position_str, NULL, 10); // Attempt to convert
}
g_free(position_str);
}
g_free(num_pat);
return position;
}

View file

@ -19,6 +19,8 @@
void koto_track_helpers_init();
guint64 koto_track_helpers_get_cd_based_on_file_name(const gchar * file_name);
gchar * koto_track_helpers_get_corrected_genre(gchar * original_genre);
gchar * koto_track_helpers_get_name_for_file(

View file

@ -39,7 +39,11 @@ struct _KotoTrack {
guint cd;
guint64 position;
guint64 duration;
guint64 * playback_position;
gchar * description;
gchar * narrator;
guint64 playback_position;
guint64 year;
GList * genres;
gboolean do_initial_index;
@ -57,6 +61,9 @@ enum {
PROP_CD,
PROP_POSITION,
PROP_DURATION,
PROP_DESCRIPTION,
PROP_NARRATOR,
PROP_YEAR,
PROP_PLAYBACK_POSITION,
PROP_PREPARSED_GENRES,
N_PROPERTIES
@ -157,6 +164,32 @@ static void koto_track_class_init(KotoTrackClass * c) {
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
);
props[PROP_DESCRIPTION] = g_param_spec_string(
"description",
"Description of Track, typically show notes for a podcast episode",
"Description of Track, typically show notes for a podcast episode",
NULL,
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
);
props[PROP_NARRATOR] = g_param_spec_string(
"narrator",
"Narrator, typically of an Audiobook",
"Narrator, typically of an Audiobook",
NULL,
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
);
props[PROP_YEAR] = g_param_spec_uint64(
"year",
"Year",
"Year",
0,
G_MAXUINT64,
0,
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
);
props[PROP_PLAYBACK_POSITION] = g_param_spec_uint64(
"playback-position",
"Current playback position",
@ -179,10 +212,13 @@ static void koto_track_class_init(KotoTrackClass * c) {
}
static void koto_track_init(KotoTrack * self) {
self->description = NULL; // Initialize our description
self->duration = 0; // Initialize our duration
self->genres = NULL; // Initialize our genres list
self->narrator = NULL, // Initialize our narrator
self->paths = g_hash_table_new(g_str_hash, g_str_equal); // Create our hash table of paths
self->position = 0; // Initialize our duration
self->year = 0; // Initialize our year
}
static void koto_track_get_property(
@ -209,11 +245,20 @@ static void koto_track_get_property(
case PROP_CD:
g_value_set_uint(val, self->cd);
break;
case PROP_DESCRIPTION:
g_value_set_string(val, self->description);
break;
case PROP_NARRATOR:
g_value_set_string(val, self->narrator);
break;
case PROP_YEAR:
g_value_set_uint64(val, self->year);
break;
case PROP_POSITION:
g_value_set_uint(val, self->position);
g_value_set_uint64(val, self->position);
break;
case PROP_PLAYBACK_POSITION:
g_value_set_uint(val, GPOINTER_TO_UINT(self->playback_position));
g_value_set_uint64(val, koto_track_get_playback_position(self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
@ -254,11 +299,20 @@ static void koto_track_set_property(
koto_track_set_position(self, g_value_get_uint64(val));
break;
case PROP_PLAYBACK_POSITION:
self->playback_position = GUINT_TO_POINTER(g_value_get_uint64(val));
koto_track_set_playback_position(self, g_value_get_uint64(val));
break;
case PROP_DURATION:
koto_track_set_duration(self, g_value_get_uint64(val));
break;
case PROP_DESCRIPTION:
koto_track_set_description(self, g_value_get_string(val));
break;
case PROP_NARRATOR:
koto_track_set_narrator(self, g_strdup(g_value_get_string(val)));
break;
case PROP_YEAR:
koto_track_set_year(self, g_value_get_uint64(val));
break;
case PROP_PREPARSED_GENRES:
koto_track_set_preparsed_genres(self, g_strdup(g_value_get_string(val)));
break;
@ -273,14 +327,10 @@ void koto_track_commit(KotoTrack * self) {
return;
}
if (!koto_utils_is_string_valid(self->artist_uuid)) { // No valid required artist UUID
if (!koto_utils_string_is_valid(self->artist_uuid)) { // No valid required artist UUID
return;
}
if (!koto_utils_is_string_valid(self->album_uuid)) { // If we do not have a valid album UUID
self->album_uuid = g_strdup("");
}
gchar * commit_msg = "INSERT INTO tracks(id, artist_id, album_id, name, disc, position, duration, genres)" \
"VALUES('%s', '%s', '%s', quote(\"%s\"), %d, %d, %d, '%s')" \
"ON CONFLICT(id) DO UPDATE SET album_id=excluded.album_id, artist_id=excluded.artist_id, name=excluded.name, disc=excluded.disc, position=excluded.position, duration=excluded.duration, genres=excluded.genres;";
@ -292,7 +342,7 @@ void koto_track_commit(KotoTrack * self) {
commit_msg,
self->uuid,
self->artist_uuid,
self->album_uuid,
koto_utils_string_get_valid(self->album_uuid),
g_strescape(self->parsed_name, NULL),
(int) self->cd,
(int) self->position,
@ -326,6 +376,10 @@ void koto_track_commit(KotoTrack * self) {
}
}
gchar * koto_track_get_description(KotoTrack * self) {
return KOTO_IS_TRACK(self) ? g_strdup(self->description) : NULL;
}
guint koto_track_get_disc_number(KotoTrack * self) {
return KOTO_IS_TRACK(self) ? self->cd : 1;
}
@ -347,14 +401,14 @@ GVariant * koto_track_get_metadata_vardict(KotoTrack * self) {
KotoArtist * artist = koto_cartographer_get_artist_by_uuid(koto_maps, self->artist_uuid);
gchar * artist_name = koto_artist_get_name(artist);
if (koto_utils_is_string_valid(self->album_uuid)) { // Have an album associated
if (koto_utils_string_is_valid(self->album_uuid)) { // Have an album associated
KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, self->album_uuid);
if (KOTO_IS_ALBUM(album)) {
gchar * album_art_path = koto_album_get_art(album);
gchar * album_name = koto_album_get_name(album);
if (koto_utils_is_string_valid(album_art_path)) { // Valid album art path
if (koto_utils_string_is_valid(album_art_path)) { // Valid album art path
album_art_path = g_strconcat("file://", album_art_path, NULL); // Prepend with file://
g_variant_builder_add(builder, "{sv}", "mpris:artUrl", g_variant_new_string(album_art_path));
}
@ -366,7 +420,7 @@ GVariant * koto_track_get_metadata_vardict(KotoTrack * self) {
g_variant_builder_add(builder, "{sv}", "mpris:trackid", g_variant_new_string(self->uuid));
if (koto_utils_is_string_valid(artist_name)) { // Valid artist name
if (koto_utils_string_is_valid(artist_name)) { // Valid artist name
GVariant * artist_name_variant;
GVariantBuilder * artist_list_builder = g_variant_builder_new(G_VARIANT_TYPE("as"));
g_variant_builder_add(artist_list_builder, "s", artist_name);
@ -388,11 +442,11 @@ GVariant * koto_track_get_metadata_vardict(KotoTrack * self) {
}
gchar * koto_track_get_name(KotoTrack * self) {
if (!KOTO_IS_TRACK(self)) { // Not a track
return NULL;
}
return KOTO_IS_TRACK(self) ? g_strdup(self->parsed_name) : NULL;
}
return g_strdup(self->parsed_name);
gchar * koto_track_get_narrator(KotoTrack * self) {
return KOTO_IS_TRACK(self) ? g_strdup(self->narrator) : NULL;
}
gchar * koto_track_get_path(KotoTrack * self) {
@ -420,6 +474,10 @@ gchar * koto_track_get_path(KotoTrack * self) {
return path;
}
guint64 koto_track_get_playback_position(KotoTrack * self) {
return KOTO_IS_TRACK(self) ? self->playback_position : 0;
}
guint64 koto_track_get_position(KotoTrack * self) {
return KOTO_IS_TRACK(self) ? self->position : 0;
}
@ -433,13 +491,13 @@ gchar * koto_track_get_uniqueish_key(KotoTrack * self) {
gchar * artist_name = koto_artist_get_name(artist); // Get the artist name
if (koto_utils_is_string_valid(self->album_uuid)) { // If we have an album associated with this track (not necessarily guaranteed)
if (koto_utils_string_is_valid(self->album_uuid)) { // If we have an album associated with this track (not necessarily guaranteed)
KotoAlbum * possible_album = koto_cartographer_get_album_by_uuid(koto_maps, self->album_uuid);
if (KOTO_IS_ALBUM(possible_album)) { // Album exists
gchar * album_name = koto_album_get_name(possible_album); // Get the name of the album
if (koto_utils_is_string_valid(album_name)) {
if (koto_utils_string_is_valid(album_name)) {
return g_strdup_printf("%s-%s-%s", artist_name, album_name, self->parsed_name); // Create a key of (ARTIST/WRITER)-(ALBUM/AUDIOBOOK)-(CHAPTER/TRACK)
}
}
@ -456,6 +514,14 @@ gchar * koto_track_get_uuid(KotoTrack * self) {
return self->uuid; // Do not return a duplicate since otherwise comparison refs fail due to pointer positions being different
}
guint64 koto_track_get_year(KotoTrack * self) {
if (!KOTO_IS_TRACK(self)) {
return 0;
}
return self->year;
}
void koto_track_remove_from_playlist(
KotoTrack * self,
gchar * playlist_uuid
@ -487,7 +553,7 @@ void koto_track_set_album_uuid(
gchar * uuid = g_strdup(album_uuid);
if (!koto_utils_is_string_valid(uuid)) { // If this is not a valid string
if (!koto_utils_string_is_valid(uuid)) { // If this is not a valid string
return;
}
@ -497,19 +563,17 @@ void koto_track_set_album_uuid(
void koto_track_save_to_playlist(
KotoTrack * self,
gchar * playlist_uuid,
gint current
gchar * playlist_uuid
) {
if (!KOTO_IS_TRACK(self)) {
return;
}
gchar * commit_op = g_strdup_printf(
"INSERT INTO playlist_tracks(playlist_id, track_id, current)"
"VALUES('%s', '%s', quote(\"%d\"))",
"INSERT INTO playlist_tracks(playlist_id, track_id)"
"VALUES('%s', '%s')",
playlist_uuid,
self->uuid,
current
self->uuid
);
new_transaction(commit_op, "Failed to save track to playlist", FALSE);
@ -531,6 +595,30 @@ void koto_track_set_cd(
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_CD]);
}
void koto_track_set_description(
KotoTrack * self,
const gchar * description
) {
if (!KOTO_IS_TRACK(self)) {
return;
}
if (!koto_utils_string_is_valid(description)) {
return;
}
if (g_strcmp0(self->description, description) == 0) { // Same description
return;
}
if (koto_utils_string_is_valid(self->description)) {
g_free(self->description); // Free the existing narrator
}
self->description = g_strdup(description); // Duplicate our description
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_DESCRIPTION]);
}
void koto_track_set_duration(
KotoTrack * self,
guint64 duration
@ -544,6 +632,7 @@ void koto_track_set_duration(
}
self->duration = duration;
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_DURATION]);
}
void koto_track_set_genres(
@ -554,7 +643,7 @@ void koto_track_set_genres(
return;
}
if (!koto_utils_is_string_valid((gchar*) genrelist)) { // If it is an empty string
if (!koto_utils_string_is_valid((gchar*) genrelist)) { // If it is an empty string
return;
}
@ -570,7 +659,7 @@ void koto_track_set_genres(
gchar * genre = genres[i]; // Get the genre
gchar * lowercased_genre = g_utf8_strdown(g_strstrip(genre), -1); // Lowercase the genre
gchar * lowercased_hyphenated_genre = koto_utils_replace_string_all(lowercased_genre, " ", "-");
gchar * lowercased_hyphenated_genre = koto_utils_string_replace_all(lowercased_genre, " ", "-");
g_free(lowercased_genre); // Free the lowercase genre string since we no longer need it
gchar * corrected_genre = koto_track_helpers_get_corrected_genre(lowercased_hyphenated_genre); // Get any corrected genre
@ -585,6 +674,30 @@ void koto_track_set_genres(
g_strfreev(genres); // Free the list of genres locally
}
void koto_track_set_narrator(
KotoTrack * self,
const gchar * narrator
) {
if (!KOTO_IS_TRACK(self)) {
return;
}
if (!koto_utils_string_is_valid(narrator)) {
return;
}
if (g_strcmp0(self->narrator, narrator) == 0) { // Same narrator
return;
}
if (koto_utils_string_is_valid(self->narrator)) {
g_free(self->narrator); // Free the existing narrator
}
self->narrator = g_strdup(narrator); // Duplicate our narrator
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_NARRATOR]);
}
void koto_track_set_parsed_name(
KotoTrack * self,
gchar * new_parsed_name
@ -593,11 +706,11 @@ void koto_track_set_parsed_name(
return;
}
if (!koto_utils_is_string_valid(new_parsed_name)) {
if (!koto_utils_string_is_valid(new_parsed_name)) {
return;
}
gboolean have_existing_name = koto_utils_is_string_valid(self->parsed_name);
gboolean have_existing_name = koto_utils_string_is_valid(self->parsed_name);
if (have_existing_name && (strcmp(self->parsed_name, new_parsed_name) == 0)) { // Have existing name that matches one provided
return; // Don't do anything
@ -620,7 +733,7 @@ void koto_track_set_path(
return;
}
if (!koto_utils_is_string_valid(fixed_path)) { // Not a valid path
if (!koto_utils_string_is_valid(fixed_path)) { // Not a valid path
return;
}
@ -635,10 +748,25 @@ void koto_track_set_path(
}
}
void koto_track_set_playback_position(
KotoTrack * self,
guint64 position
) {
if (!KOTO_IS_TRACK(self)) { // Not a track
return;
}
self->playback_position = position;
}
void koto_track_set_position(
KotoTrack * self,
guint64 pos
) {
if (!KOTO_IS_TRACK(self)) { // Not a track
return;
}
if (pos == 0) { // No position change really
return;
}
@ -647,29 +775,48 @@ void koto_track_set_position(
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_POSITION]);
}
void koto_track_set_year(
KotoTrack * self,
guint64 year
) {
if (!KOTO_IS_TRACK(self)) {
return;
}
self->year = year;
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_YEAR]);
}
void koto_track_update_metadata(KotoTrack * self) {
if (!KOTO_IS_TRACK(self)) { // Not a track
return;
}
gchar * optimal_track_path = koto_track_get_path(self); // Check all the libraries associated with this track, based on priority, return a built path using lib path + relative file path
if (!koto_utils_string_is_valid(optimal_track_path)) { // Not a valid string
return;
}
TagLib_File * t_file = taglib_file_new(optimal_track_path); // Get a taglib file for this file
g_free(optimal_track_path);
if ((t_file != NULL) && taglib_file_is_valid(t_file)) { // If we got the taglib file and it is valid
TagLib_Tag * tag = taglib_file_tag(t_file); // Get our tag
koto_track_set_genres(self, taglib_tag_genre(tag)); // Set our genres to any genres listed for the track
koto_track_set_position(self, (uint) taglib_tag_track(tag)); // Get the track, convert to uint and cast as a pointer
koto_track_set_year(self, (guint64) taglib_tag_year(tag)); // Get the track year and convert it to guint64
const TagLib_AudioProperties * tag_props = taglib_file_audioproperties(t_file); // Get the audio properties of the file
koto_track_set_duration(self, taglib_audioproperties_length(tag_props)); // Get the length of the track and set it as our duration
} else { // Failed to get tag info
}
if (self->position == 0) { // Failed to get tag info or got the tag info but position is zero
guint64 position = koto_track_helpers_get_position_based_on_file_name(g_path_get_basename(optimal_track_path)); // Get the likely position
koto_track_set_position(self, position); // Set our position
}
taglib_tag_free_strings(); // Free strings
taglib_file_free(t_file); // Free the file
g_free(optimal_track_path);
}
void koto_track_set_preparsed_genres(
@ -680,7 +827,7 @@ void koto_track_set_preparsed_genres(
return;
}
if (!koto_utils_is_string_valid(genrelist)) { // If it is an empty string
if (!koto_utils_string_is_valid(genrelist)) { // If it is an empty string
return;
}

View file

@ -17,7 +17,7 @@
#include <glib-2.0/glib-object.h>
#include <gtk-4.0/gtk/gtk.h>
#include "koto-button.h"
#include "components/button.h"
#include "koto-dialog-container.h"
struct _KotoDialogContainer {

View file

@ -17,8 +17,8 @@
#include <glib/gi18n.h>
#include <gtk-4.0/gtk/gtk.h>
#include "components/button.h"
#include "config/config.h"
#include "koto-button.h"
#include "koto-expander.h"
#include "koto-utils.h"
@ -212,7 +212,7 @@ void koto_expander_set_icon_name(
return;
}
if (!koto_utils_is_string_valid(icon)) { // Not a valid string
if (!koto_utils_string_is_valid(icon)) { // Not a valid string
return;
}

View file

@ -18,7 +18,7 @@
#pragma once
#include <gtk-4.0/gtk/gtk.h>
#include "koto-button.h"
#include "components/button.h"
G_BEGIN_DECLS

View file

@ -16,11 +16,11 @@
*/
#include <gtk-4.0/gtk/gtk.h>
#include "components/button.h"
#include "config/config.h"
#include "db/cartographer.h"
#include "indexer/structs.h"
#include "playlist/playlist.h"
#include "config/config.h"
#include "koto-button.h"
#include "koto-expander.h"
#include "koto-nav.h"
#include "koto-utils.h"
@ -112,12 +112,17 @@ void koto_nav_create_audiobooks_section(KotoNav * self) {
koto_expander_set_content(a_expander, new_content);
self->audiobooks_local = koto_button_new_plain("Library");
koto_button_set_data(self->audiobooks_local, &"audiobooks.library");
self->audiobooks_audible = koto_button_new_plain("Audible");
self->audiobooks_librivox = koto_button_new_plain("LibriVox");
gtk_box_append(GTK_BOX(new_content), GTK_WIDGET(self->audiobooks_local));
gtk_box_append(GTK_BOX(new_content), GTK_WIDGET(self->audiobooks_audible));
gtk_box_append(GTK_BOX(new_content), GTK_WIDGET(self->audiobooks_librivox));
koto_button_add_click_handler(self->audiobooks_local, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_button_global_page_nav_callback), self->audiobooks_local);
}
void koto_nav_create_music_section(KotoNav * self) {
@ -129,13 +134,15 @@ void koto_nav_create_music_section(KotoNav * self) {
GtkWidget * new_content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
self->music_local = koto_button_new_plain("Library");
koto_button_set_data(self->music_local, &"music.library");
self->music_radio = koto_button_new_plain("Radio");
gtk_box_append(GTK_BOX(new_content), GTK_WIDGET(self->music_local));
gtk_box_append(GTK_BOX(new_content), GTK_WIDGET(self->music_radio));
koto_expander_set_content(m_expander, new_content);
koto_button_add_click_handler(self->music_local, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_nav_handle_local_music_click), NULL);
koto_button_add_click_handler(self->music_local, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_button_global_page_nav_callback), self->music_local);
}
void koto_nav_create_playlist_section(KotoNav * self) {
@ -187,37 +194,6 @@ void koto_nav_handle_playlist_add_click(
koto_window_show_dialog(main_window, "create-modify-playlist");
}
void koto_nav_handle_local_music_click(
GtkGestureClick * gesture,
int n_press,
double x,
double y,
gpointer user_data
) {
(void) gesture;
(void) n_press;
(void) x;
(void) y;
(void) user_data;
koto_window_go_to_page(main_window, "music.local"); // Go to the playlist page
}
void koto_nav_handle_playlist_button_click(
GtkGestureClick * gesture,
int n_press,
double x,
double y,
gpointer user_data
) {
(void) gesture;
(void) n_press;
(void) x;
(void) y;
gchar * playlist_uuid = user_data;
koto_window_go_to_page(main_window, playlist_uuid); // Go to the playlist page
}
void koto_nav_handle_playlist_added(
KotoCartographer * carto,
KotoPlaylist * playlist,
@ -228,6 +204,10 @@ void koto_nav_handle_playlist_added(
return;
}
if (koto_playlist_get_is_hidden(playlist)) { // Should be hidden from nav
return;
}
KotoNav * self = user_data;
if (!KOTO_IS_NAV(self)) {
@ -245,25 +225,28 @@ void koto_nav_handle_playlist_added(
gchar * playlist_art_path = koto_playlist_get_artwork(playlist); // Get any file path for it
KotoButton * playlist_button = NULL;
if (koto_utils_is_string_valid(playlist_art_path)) { // Have a file associated
if (koto_utils_string_is_valid(playlist_art_path)) { // Have a file associated
playlist_button = koto_button_new_with_file(playlist_name, playlist_art_path, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
} else { // No file associated
playlist_button = koto_button_new_with_icon(playlist_name, "audio-x-generic-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
}
if (KOTO_IS_BUTTON(playlist_button)) {
g_hash_table_insert(self->playlist_buttons, playlist_uuid, playlist_button); // Add the button
if (!KOTO_IS_BUTTON(playlist_button)) { // Failed to create the playlist button
return;
}
// TODO: Make this a ListBox and sort the playlists alphabetically
GtkBox * playlist_expander_content = GTK_BOX(koto_expander_get_content(self->playlists_expander));
koto_button_set_data(playlist_button, playlist_uuid); // Set our data to the playlist UUID string
g_hash_table_insert(self->playlist_buttons, playlist_uuid, playlist_button); // Add the button
if (GTK_IS_BOX(playlist_expander_content)) {
gtk_box_append(playlist_expander_content, GTK_WIDGET(playlist_button));
// TODO: Make this a ListBox and sort the playlists alphabetically
GtkBox * playlist_expander_content = GTK_BOX(koto_expander_get_content(self->playlists_expander));
koto_button_add_click_handler(playlist_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_nav_handle_playlist_button_click), playlist_uuid);
koto_window_handle_playlist_added(koto_maps, playlist, main_window); // TODO: MOVE THIS
g_signal_connect(playlist, "modified", G_CALLBACK(koto_nav_handle_playlist_modified), self);
}
if (GTK_IS_BOX(playlist_expander_content)) {
gtk_box_append(playlist_expander_content, GTK_WIDGET(playlist_button));
koto_button_add_click_handler(playlist_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_button_global_page_nav_callback), playlist_button);
koto_window_handle_playlist_added(koto_maps, playlist, main_window); // TODO: MOVE THIS
g_signal_connect(playlist, "modified", G_CALLBACK(koto_nav_handle_playlist_modified), self);
}
}
@ -291,13 +274,13 @@ void koto_nav_handle_playlist_modified(
gchar * artwork = koto_playlist_get_artwork(playlist); // Get the artwork
if (koto_utils_is_string_valid(artwork)) { // Have valid artwork
if (koto_utils_string_is_valid(artwork)) { // Have valid artwork
koto_button_set_file_path(playlist_button, artwork); // Update the artwork path
}
gchar * name = koto_playlist_get_name(playlist); // Get the name
if (koto_utils_is_string_valid(name)) { // Have valid name
if (koto_utils_string_is_valid(name)) { // Have valid name
koto_button_set_text(playlist_button, name); // Update the button text
}
}

View file

@ -60,14 +60,6 @@ void koto_nav_handle_playlist_removed(
gpointer user_data
);
void koto_nav_handle_local_music_click(
GtkGestureClick * gesture,
int n_press,
double x,
double y,
gpointer user_data
);
GtkWidget * koto_nav_get_nav(KotoNav * self);
G_END_DECLS

View file

@ -17,13 +17,13 @@
#include <gstreamer-1.0/gst/gst.h>
#include <gtk-4.0/gtk/gtk.h>
#include "components/button.h"
#include "config/config.h"
#include "db/cartographer.h"
#include "playlist/add-remove-track-popover.h"
#include "playlist/current.h"
#include "playlist/playlist.h"
#include "playback/engine.h"
#include "koto-button.h"
#include "config/config.h"
#include "koto-playerbar.h"
#include "koto-utils.h"
@ -64,6 +64,9 @@ struct _KotoPlayerBar {
GtkWidget * playback_album;
GtkWidget * playback_artist;
/* Misc Widgets */
GtkWidget * playback_position_label;
gint64 last_recorded_duration;
gboolean is_progressbar_seeking;
@ -177,98 +180,104 @@ void koto_playerbar_apply_configuration_state(
gtk_scale_button_set_value(GTK_SCALE_BUTTON(self->volume_button), config_last_used_volume * 100);
}
void koto_playerbar_create_playback_details(KotoPlayerBar * bar) {
bar->playback_details_section = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
void koto_playerbar_create_playback_details(KotoPlayerBar * self) {
self->playback_details_section = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
GtkIconTheme * default_icon_theme = gtk_icon_theme_get_for_display(gdk_display_get_default()); // Get the icon theme for this display
if (default_icon_theme != NULL) {
gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(bar->main));
gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(self->main));
GtkIconPaintable * audio_paintable = gtk_icon_theme_lookup_icon(default_icon_theme, "audio-x-generic-symbolic", NULL, 96, scale_factor, GTK_TEXT_DIR_NONE, GTK_ICON_LOOKUP_PRELOAD);
if (GTK_IS_ICON_PAINTABLE(audio_paintable)) {
if (GTK_IS_IMAGE(bar->artwork)) {
gtk_image_set_from_paintable(GTK_IMAGE(bar->artwork), GDK_PAINTABLE(audio_paintable));
if (GTK_IS_IMAGE(self->artwork)) {
gtk_image_set_from_paintable(GTK_IMAGE(self->artwork), GDK_PAINTABLE(audio_paintable));
} else { // Not an image
bar->artwork = gtk_image_new_from_paintable(GDK_PAINTABLE(audio_paintable));
gtk_widget_add_css_class(bar->artwork, "circular");
gtk_widget_set_size_request(bar->artwork, 96, 96);
gtk_box_append(GTK_BOX(bar->playback_section), bar->artwork);
self->artwork = gtk_image_new_from_paintable(GDK_PAINTABLE(audio_paintable));
gtk_widget_add_css_class(self->artwork, "circular");
gtk_widget_set_size_request(self->artwork, 96, 96);
gtk_box_append(GTK_BOX(self->playback_section), self->artwork);
}
}
}
bar->playback_title = gtk_label_new("Title");
gtk_label_set_xalign(GTK_LABEL(bar->playback_title), 0);
self->playback_title = gtk_label_new("Title");
gtk_label_set_xalign(GTK_LABEL(self->playback_title), 0);
bar->playback_album = gtk_label_new("Album");
gtk_label_set_xalign(GTK_LABEL(bar->playback_album), 0);
self->playback_album = gtk_label_new("Album");
gtk_label_set_xalign(GTK_LABEL(self->playback_album), 0);
bar->playback_artist = gtk_label_new("Artist");
gtk_label_set_xalign(GTK_LABEL(bar->playback_artist), 0);
self->playback_artist = gtk_label_new("Artist");
gtk_label_set_xalign(GTK_LABEL(self->playback_artist), 0);
gtk_box_append(GTK_BOX(bar->playback_details_section), GTK_WIDGET(bar->playback_title));
gtk_box_append(GTK_BOX(bar->playback_details_section), GTK_WIDGET(bar->playback_album));
gtk_box_append(GTK_BOX(bar->playback_details_section), GTK_WIDGET(bar->playback_artist));
gtk_box_append(GTK_BOX(self->playback_details_section), GTK_WIDGET(self->playback_title));
gtk_box_append(GTK_BOX(self->playback_details_section), GTK_WIDGET(self->playback_album));
gtk_box_append(GTK_BOX(self->playback_details_section), GTK_WIDGET(self->playback_artist));
gtk_box_append(GTK_BOX(bar->playback_section), GTK_WIDGET(bar->playback_details_section));
gtk_box_append(GTK_BOX(self->playback_section), GTK_WIDGET(self->playback_details_section));
}
void koto_playerbar_create_primary_controls(KotoPlayerBar * bar) {
bar->back_button = koto_button_new_with_icon("", "media-skip-backward-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
bar->play_pause_button = koto_button_new_with_icon("", "media-playback-start-symbolic", "media-playback-pause-symbolic", KOTO_BUTTON_PIXBUF_SIZE_LARGE); // TODO: Have this take in a state and switch to a different icon if necessary
bar->forward_button = koto_button_new_with_icon("", "media-skip-forward-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
void koto_playerbar_create_primary_controls(KotoPlayerBar * self) {
self->back_button = koto_button_new_with_icon("", "media-skip-backward-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
self->play_pause_button = koto_button_new_with_icon("", "media-playback-start-symbolic", "media-playback-pause-symbolic", KOTO_BUTTON_PIXBUF_SIZE_LARGE); // TODO: Have this take in a state and switch to a different icon if necessary
self->forward_button = koto_button_new_with_icon("", "media-skip-forward-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
if (KOTO_IS_BUTTON(bar->back_button)) {
gtk_box_append(GTK_BOX(bar->primary_controls_section), GTK_WIDGET(bar->back_button));
koto_button_add_click_handler(bar->back_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_go_backwards), bar);
if (KOTO_IS_BUTTON(self->back_button)) {
gtk_box_append(GTK_BOX(self->primary_controls_section), GTK_WIDGET(self->back_button));
koto_button_add_click_handler(self->back_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_go_backwards), self);
}
if (KOTO_IS_BUTTON(bar->play_pause_button)) {
gtk_box_append(GTK_BOX(bar->primary_controls_section), GTK_WIDGET(bar->play_pause_button));
koto_button_add_click_handler(bar->play_pause_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_toggle_play_pause), bar);
if (KOTO_IS_BUTTON(self->play_pause_button)) {
gtk_box_append(GTK_BOX(self->primary_controls_section), GTK_WIDGET(self->play_pause_button));
koto_button_add_click_handler(self->play_pause_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_toggle_play_pause), self);
}
if (KOTO_IS_BUTTON(bar->forward_button)) {
gtk_box_append(GTK_BOX(bar->primary_controls_section), GTK_WIDGET(bar->forward_button));
koto_button_add_click_handler(bar->forward_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_go_forwards), bar);
if (KOTO_IS_BUTTON(self->forward_button)) {
gtk_box_append(GTK_BOX(self->primary_controls_section), GTK_WIDGET(self->forward_button));
koto_button_add_click_handler(self->forward_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_go_forwards), self);
}
}
void koto_playerbar_create_secondary_controls(KotoPlayerBar * bar) {
bar->repeat_button = koto_button_new_with_icon("", "media-playlist-repeat-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
bar->shuffle_button = koto_button_new_with_icon("", "media-playlist-shuffle-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
bar->playlist_button = koto_button_new_with_icon("", "playlist-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
bar->eq_button = koto_button_new_with_icon("", "multimedia-equalizer-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
void koto_playerbar_create_secondary_controls(KotoPlayerBar * self) {
self->playback_position_label = gtk_label_new(NULL); // Create our label to communicate position and duration of a track
bar->volume_button = gtk_volume_button_new(); // Have this take in a state and switch to a different icon if necessary
self->repeat_button = koto_button_new_with_icon("", "media-playlist-repeat-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
self->shuffle_button = koto_button_new_with_icon("", "media-playlist-shuffle-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
self->playlist_button = koto_button_new_with_icon("", "playlist-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
self->eq_button = koto_button_new_with_icon("", "multimedia-equalizer-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
if (KOTO_IS_BUTTON(bar->repeat_button)) {
gtk_box_append(GTK_BOX(bar->secondary_controls_section), GTK_WIDGET(bar->repeat_button));
koto_button_add_click_handler(bar->repeat_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_toggle_track_repeat), bar);
self->volume_button = gtk_volume_button_new(); // Have this take in a state and switch to a different icon if necessary
if (GTK_IS_LABEL(self->playback_position_label)) {
gtk_box_append(GTK_BOX(self->secondary_controls_section), self->playback_position_label);
}
if (KOTO_IS_BUTTON(bar->shuffle_button)) {
gtk_box_append(GTK_BOX(bar->secondary_controls_section), GTK_WIDGET(bar->shuffle_button));
koto_button_add_click_handler(bar->shuffle_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_toggle_playlist_shuffle), bar);
if (KOTO_IS_BUTTON(self->repeat_button)) {
gtk_box_append(GTK_BOX(self->secondary_controls_section), GTK_WIDGET(self->repeat_button));
koto_button_add_click_handler(self->repeat_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_toggle_track_repeat), self);
}
if (KOTO_IS_BUTTON(bar->playlist_button)) {
gtk_box_append(GTK_BOX(bar->secondary_controls_section), GTK_WIDGET(bar->playlist_button));
koto_button_add_click_handler(bar->playlist_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_handle_playlist_button_clicked), bar);
if (KOTO_IS_BUTTON(self->shuffle_button)) {
gtk_box_append(GTK_BOX(self->secondary_controls_section), GTK_WIDGET(self->shuffle_button));
koto_button_add_click_handler(self->shuffle_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_toggle_playlist_shuffle), self);
}
if (KOTO_IS_BUTTON(bar->eq_button)) {
gtk_box_append(GTK_BOX(bar->secondary_controls_section), GTK_WIDGET(bar->eq_button));
if (KOTO_IS_BUTTON(self->playlist_button)) {
gtk_box_append(GTK_BOX(self->secondary_controls_section), GTK_WIDGET(self->playlist_button));
koto_button_add_click_handler(self->playlist_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_handle_playlist_button_clicked), self);
}
if (GTK_IS_VOLUME_BUTTON(bar->volume_button)) {
if (KOTO_IS_BUTTON(self->eq_button)) {
gtk_box_append(GTK_BOX(self->secondary_controls_section), GTK_WIDGET(self->eq_button));
}
if (GTK_IS_VOLUME_BUTTON(self->volume_button)) {
GtkAdjustment * granular_volume_change = gtk_adjustment_new(50.0, 0, 100.0, 1.0, 1.0, 1.0);
g_object_set(bar->volume_button, "use-symbolic", TRUE, NULL);
gtk_scale_button_set_adjustment(GTK_SCALE_BUTTON(bar->volume_button), granular_volume_change); // Set our adjustment
gtk_box_append(GTK_BOX(bar->secondary_controls_section), bar->volume_button);
g_object_set(self->volume_button, "use-symbolic", TRUE, NULL);
gtk_scale_button_set_adjustment(GTK_SCALE_BUTTON(self->volume_button), granular_volume_change); // Set our adjustment
gtk_box_append(GTK_BOX(self->secondary_controls_section), self->volume_button);
g_signal_connect(GTK_SCALE_BUTTON(bar->volume_button), "value-changed", G_CALLBACK(koto_playerbar_handle_volume_button_change), bar);
g_signal_connect(GTK_SCALE_BUTTON(self->volume_button), "value-changed", G_CALLBACK(koto_playerbar_handle_volume_button_change), self);
}
}
@ -312,13 +321,13 @@ void koto_playerbar_handle_is_playing(
return;
}
KotoPlayerBar * bar = user_data;
KotoPlayerBar * self = user_data;
if (!KOTO_IS_PLAYERBAR(bar)) {
if (!KOTO_IS_PLAYERBAR(self)) {
return;
}
koto_button_show_image(bar->play_pause_button, TRUE); // Set to TRUE to show pause as the next action
koto_button_show_image(self->play_pause_button, TRUE); // Set to TRUE to show pause as the next action
}
void koto_playerbar_handle_is_paused(
@ -329,13 +338,13 @@ void koto_playerbar_handle_is_paused(
return;
}
KotoPlayerBar * bar = user_data;
KotoPlayerBar * self = user_data;
if (!KOTO_IS_PLAYERBAR(bar)) {
if (!KOTO_IS_PLAYERBAR(self)) {
return;
}
koto_button_show_image(bar->play_pause_button, FALSE); // Set to FALSE to show play as the next action
koto_button_show_image(self->play_pause_button, FALSE); // Set to FALSE to show play as the next action
}
void koto_playerbar_handle_playlist_button_clicked(
@ -366,13 +375,13 @@ void koto_playerbar_handle_progressbar_gesture_begin(
) {
(void) gesture;
(void) seq;
KotoPlayerBar * bar = data;
KotoPlayerBar * self = data;
if (!KOTO_IS_PLAYERBAR(bar)) {
if (!KOTO_IS_PLAYERBAR(self)) {
return;
}
bar->is_progressbar_seeking = TRUE;
self->is_progressbar_seeking = TRUE;
}
void koto_playerbar_handle_progressbar_gesture_end(
@ -382,12 +391,12 @@ void koto_playerbar_handle_progressbar_gesture_end(
) {
(void) gesture;
(void) seq;
KotoPlayerBar * bar = data;
KotoPlayerBar * self = data;
if (!KOTO_IS_PLAYERBAR(bar)) {
if (!KOTO_IS_PLAYERBAR(self)) {
return;
}
bar->is_progressbar_seeking = FALSE;
self->is_progressbar_seeking = FALSE;
}
void koto_playerbar_handle_progressbar_pressed(
@ -401,26 +410,26 @@ void koto_playerbar_handle_progressbar_pressed(
(void) n_press;
(void) x;
(void) y;
KotoPlayerBar * bar = data;
KotoPlayerBar * self = data;
if (!KOTO_IS_PLAYERBAR(bar)) {
if (!KOTO_IS_PLAYERBAR(self)) {
return;
}
bar->is_progressbar_seeking = TRUE;
self->is_progressbar_seeking = TRUE;
}
void koto_playerbar_handle_progressbar_value_changed(
GtkRange * progress_bar,
gpointer data
) {
KotoPlayerBar * bar = data;
KotoPlayerBar * self = data;
if (!KOTO_IS_PLAYERBAR(bar)) {
if (!KOTO_IS_PLAYERBAR(self)) {
return;
}
if (!bar->is_progressbar_seeking) {
if (!self->is_progressbar_seeking) {
return;
}
@ -437,13 +446,13 @@ void koto_playerbar_handle_tick_duration(
return;
}
KotoPlayerBar * bar = user_data;
KotoPlayerBar * self = user_data;
if (!KOTO_IS_PLAYERBAR(bar)) {
if (!KOTO_IS_PLAYERBAR(self)) {
return;
}
koto_playerbar_set_progressbar_duration(bar, koto_playback_engine_get_duration(engine));
koto_playerbar_set_progressbar_duration(self, koto_playback_engine_get_duration(engine));
}
void koto_playerbar_handle_tick_track(
@ -454,15 +463,27 @@ void koto_playerbar_handle_tick_track(
return;
}
KotoPlayerBar * bar = user_data;
KotoPlayerBar * self = user_data;
if (!KOTO_IS_PLAYERBAR(bar)) {
if (!KOTO_IS_PLAYERBAR(self)) {
return;
}
if (!bar->is_progressbar_seeking) {
koto_playerbar_set_progressbar_value(bar, koto_playback_engine_get_progress(engine));
if (self->is_progressbar_seeking) { // Currently seeking
return;
}
KotoTrack * current_track = koto_playback_engine_get_current_track(engine);
gtk_label_set_text(
GTK_LABEL(self->playback_position_label),
g_strdup_printf(
"%s / %s",
koto_utils_seconds_to_time_format(koto_track_get_playback_position(current_track)),
koto_utils_seconds_to_time_format(koto_track_get_duration(current_track))
));
koto_playerbar_set_progressbar_value(self, koto_playback_engine_get_progress(engine));
}
void koto_playerbar_handle_track_repeat(
@ -473,16 +494,16 @@ void koto_playerbar_handle_track_repeat(
return;
}
KotoPlayerBar * bar = user_data;
KotoPlayerBar * self = user_data;
if (!KOTO_IS_PLAYERBAR(bar)) {
if (!KOTO_IS_PLAYERBAR(self)) {
return;
}
if (koto_playback_engine_get_track_repeat(engine)) { // Is repeating
gtk_widget_add_css_class(GTK_WIDGET(bar->repeat_button), "active"); // Add active CSS class
gtk_widget_add_css_class(GTK_WIDGET(self->repeat_button), "active"); // Add active CSS class
} else { // Is not repeating
gtk_widget_remove_css_class(GTK_WIDGET(bar->repeat_button), "active"); // Remove active CSS class
gtk_widget_remove_css_class(GTK_WIDGET(self->repeat_button), "active"); // Remove active CSS class
}
}
@ -494,16 +515,16 @@ void koto_playerbar_handle_track_shuffle(
return;
}
KotoPlayerBar * bar = user_data;
KotoPlayerBar * self = user_data;
if (!KOTO_IS_PLAYERBAR(bar)) {
if (!KOTO_IS_PLAYERBAR(self)) {
return;
}
if (koto_playback_engine_get_track_shuffle(engine)) { // Is repeating
gtk_widget_add_css_class(GTK_WIDGET(bar->shuffle_button), "active"); // Add active CSS class
gtk_widget_add_css_class(GTK_WIDGET(self->shuffle_button), "active"); // Add active CSS class
} else { // Is not repeating
gtk_widget_remove_css_class(GTK_WIDGET(bar->shuffle_button), "active"); // Remove active CSS class
gtk_widget_remove_css_class(GTK_WIDGET(self->shuffle_button), "active"); // Remove active CSS class
}
}
@ -517,28 +538,28 @@ void koto_playerbar_handle_volume_button_change(
koto_playback_engine_set_volume(playback_engine, (double) value / 100);
}
void koto_playerbar_reset_progressbar(KotoPlayerBar * bar) {
if (!KOTO_IS_PLAYERBAR(bar)) {
void koto_playerbar_reset_progressbar(KotoPlayerBar * self) {
if (!KOTO_IS_PLAYERBAR(self)) {
return;
}
if (!GTK_IS_RANGE(bar->progress_bar)) {
if (!GTK_IS_RANGE(self->progress_bar)) {
return;
}
gtk_range_set_range(GTK_RANGE(bar->progress_bar), 0, 0); // Reset range
gtk_range_set_value(GTK_RANGE(bar->progress_bar), 0); // Set value to 0
gtk_range_set_range(GTK_RANGE(self->progress_bar), 0, 0); // Reset range
gtk_range_set_value(GTK_RANGE(self->progress_bar), 0); // Set value to 0
}
void koto_playerbar_set_progressbar_duration(
KotoPlayerBar * bar,
KotoPlayerBar * self,
gint64 duration
) {
if (!KOTO_IS_PLAYERBAR(bar)) {
if (!KOTO_IS_PLAYERBAR(self)) {
return;
}
if (!GTK_IS_RANGE(bar->progress_bar)) {
if (!GTK_IS_RANGE(self->progress_bar)) {
return;
}
@ -546,25 +567,25 @@ void koto_playerbar_set_progressbar_duration(
return;
}
if (duration != bar->last_recorded_duration) { // Duration is different than what we recorded
bar->last_recorded_duration = duration;
gtk_range_set_range(GTK_RANGE(bar->progress_bar), 0, bar->last_recorded_duration);
if (duration != self->last_recorded_duration) { // Duration is different than what we recorded
self->last_recorded_duration = duration;
gtk_range_set_range(GTK_RANGE(self->progress_bar), 0, self->last_recorded_duration);
}
}
void koto_playerbar_set_progressbar_value(
KotoPlayerBar * bar,
KotoPlayerBar * self,
double progress
) {
if (!KOTO_IS_PLAYERBAR(bar)) {
if (!KOTO_IS_PLAYERBAR(self)) {
return;
}
if (!GTK_IS_RANGE(bar->progress_bar)) {
if (!GTK_IS_RANGE(self->progress_bar)) {
return;
}
gtk_range_set_value(GTK_RANGE(bar->progress_bar), progress);
gtk_range_set_value(GTK_RANGE(self->progress_bar), progress);
}
void koto_playerbar_toggle_play_pause(
@ -622,9 +643,9 @@ void koto_playerbar_update_track_info(
return;
}
KotoPlayerBar * bar = user_data;
KotoPlayerBar * self = user_data;
if (!KOTO_IS_PLAYERBAR(bar)) {
if (!KOTO_IS_PLAYERBAR(self)) {
return;
}
@ -644,8 +665,8 @@ void koto_playerbar_update_track_info(
g_free(artist_uuid);
if (koto_utils_is_string_valid(track_name)) { // Have a track name
gtk_label_set_text(GTK_LABEL(bar->playback_title), track_name); // Set the label
if (koto_utils_string_is_valid(track_name)) { // Have a track name
gtk_label_set_text(GTK_LABEL(self->playback_title), track_name); // Set the label
}
if (KOTO_IS_ARTIST(artist)) {
@ -653,16 +674,17 @@ void koto_playerbar_update_track_info(
g_object_get(artist, "name", &artist_name, NULL);
if ((artist_name != NULL) && (strcmp(artist_name, "") != 0)) { // Have an artist name
gtk_label_set_text(GTK_LABEL(bar->playback_artist), artist_name);
gtk_widget_show(bar->playback_artist);
gtk_label_set_text(GTK_LABEL(self->playback_artist), artist_name);
gtk_widget_show(self->playback_artist);
} else { // Don't have an artist name somehow
gtk_widget_hide(bar->playback_artist);
gtk_widget_hide(self->playback_artist);
}
}
gchar * art_path = NULL;
if (koto_utils_is_string_valid(album_uuid)) { // Have a valid album UUID
gboolean set_album_label = FALSE;
if (koto_utils_string_is_valid(album_uuid)) { // Have a valid album UUID
KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, album_uuid);
g_free(album_uuid);
@ -671,21 +693,22 @@ void koto_playerbar_update_track_info(
g_object_get(album, "name", &album_name, "art-path", &art_path, NULL); // Get album name and art path
if ((album_name != NULL) && (strcmp(album_name, "") != 0)) { // Have an album name
gtk_label_set_text(GTK_LABEL(bar->playback_album), album_name);
gtk_widget_show(bar->playback_album);
} else {
gtk_widget_hide(bar->playback_album);
gtk_label_set_text(GTK_LABEL(self->playback_album), album_name);
set_album_label = TRUE;
gtk_widget_show(self->playback_album);
}
}
}
(set_album_label) ? gtk_widget_show(self->playback_album) : gtk_widget_hide(self->playback_album);
if ((art_path != NULL) && g_path_is_absolute(art_path)) { // Have an album artist path
gtk_image_set_from_file(GTK_IMAGE(bar->artwork), art_path); // Update the art
gtk_image_set_from_file(GTK_IMAGE(self->artwork), art_path); // Update the art
} else {
gtk_image_set_from_icon_name(GTK_IMAGE(bar->artwork), "audio-x-generic-symbolic"); // Use generic instead
gtk_image_set_from_icon_name(GTK_IMAGE(self->artwork), "audio-x-generic-symbolic"); // Use generic instead
}
}
GtkWidget * koto_playerbar_get_main(KotoPlayerBar * bar) {
return bar->main;
GtkWidget * koto_playerbar_get_main(KotoPlayerBar * self) {
return self->main;
}

View file

@ -27,7 +27,7 @@ G_DECLARE_FINAL_TYPE(KotoPlayerBar, koto_playerbar, KOTO, PLAYERBAR, GObject)
#define KOTO_IS_PLAYERBAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_PLAYERBAR))
KotoPlayerBar * koto_playerbar_new(void);
GtkWidget * koto_playerbar_get_main(KotoPlayerBar * bar);
GtkWidget * koto_playerbar_get_main(KotoPlayerBar * self);
void koto_playerbar_apply_configuration_state(
KotoConfig * config,
@ -35,11 +35,11 @@ void koto_playerbar_apply_configuration_state(
KotoPlayerBar * self
);
void koto_playerbar_create_playback_details(KotoPlayerBar * bar);
void koto_playerbar_create_playback_details(KotoPlayerBar * self);
void koto_playerbar_create_primary_controls(KotoPlayerBar * bar);
void koto_playerbar_create_primary_controls(KotoPlayerBar * self);
void koto_playerbar_create_secondary_controls(KotoPlayerBar * bar);
void koto_playerbar_create_secondary_controls(KotoPlayerBar * self);
void koto_playerbar_go_backwards(
GtkGestureClick * gesture,
@ -131,15 +131,15 @@ void koto_playerbar_handle_volume_button_change(
gpointer user_data
);
void koto_playerbar_reset_progressbar(KotoPlayerBar * bar);
void koto_playerbar_reset_progressbar(KotoPlayerBar * self);
void koto_playerbar_set_progressbar_duration(
KotoPlayerBar * bar,
KotoPlayerBar * self,
gint64 duration
);
void koto_playerbar_set_progressbar_value(
KotoPlayerBar * bar,
KotoPlayerBar * self,
gdouble progress
);

View file

@ -100,6 +100,7 @@ gchar * koto_utils_get_filename_without_extension(gchar * filename) {
gchar * stripped_file_name = g_strstrip(g_strdup(trimmed_file_name)); // Strip leading and trailing whitespace
g_free(trimmed_file_name);
g_strfreev(split);
return stripped_file_name;
}
@ -111,13 +112,13 @@ gchar * koto_utils_join_string_list (
GList * cur_list;
for (cur_list = list; cur_list != NULL; cur_list = cur_list->next) { // For each item in the list
gchar * current_item = cur_list->data;
if (!koto_utils_is_string_valid(current_item)) { // Not a valid string
if (!koto_utils_string_is_valid(current_item)) { // Not a valid string
continue;
}
gchar * item_plus_sep = g_strdup_printf("%s%s", current_item, sep);
if (koto_utils_is_string_valid(liststring)) { // Is a valid string
if (koto_utils_string_is_valid(liststring)) { // Is a valid string
gchar * new_string = g_strconcat(liststring, item_plus_sep, NULL);
g_free(liststring);
liststring = new_string;
@ -128,11 +129,6 @@ gchar * koto_utils_join_string_list (
return (liststring == NULL) ? g_strdup("") : liststring;
}
gboolean koto_utils_is_string_valid(gchar * str) {
return ((str != NULL) && (g_strcmp0(str, "") != 0));
}
void koto_utils_mkdir(gchar * path) {
mkdir(path, 0755);
chown(path, getuid(), getgid());
@ -145,12 +141,37 @@ void koto_utils_push_queue_element_to_store(
g_list_store_append(G_LIST_STORE(user_data), data);
}
gchar * koto_utils_replace_string_all(
gchar * koto_utils_seconds_to_time_format(guint64 seconds) {
GDateTime * date = g_date_time_new_from_unix_utc(seconds); // Add our seconds after UTC
gchar * date_string = g_date_time_format(date, (g_date_time_get_hour(date) != 0) ? "%H:%M:%S" : "%M:%S");
g_date_time_unref(date);
return date_string;
}
gboolean koto_utils_string_contains_substring(
gchar * s,
gchar * sub
) {
gchar ** separated_string = g_strsplit(s, sub, -1); // Split on our substring
gboolean contains = (g_strv_length(separated_string) > 1);
g_strfreev(separated_string);
return contains;
}
gchar * koto_utils_string_get_valid(gchar * str) {
return koto_utils_string_is_valid(str) ? str : g_strdup(""); // Return string if a string, otherwise return an empty string
}
gboolean koto_utils_string_is_valid(const gchar * str) {
return ((str != NULL) && (g_strcmp0(str, "") != 0));
}
gchar * koto_utils_string_replace_all(
gchar * str,
gchar * find,
gchar * repl
) {
if (!koto_utils_is_string_valid(str)) { // Not a valid string
if (!koto_utils_string_is_valid(str)) { // Not a valid string
return g_strdup("");
}
@ -166,22 +187,12 @@ gchar * koto_utils_replace_string_all(
return g_strdup(g_strjoinv(repl, split));
}
gboolean koto_utils_string_contains_substring(
gchar * s,
gchar * sub
) {
gchar ** separated_string = g_strsplit(s, sub, -1); // Split on our substring
gboolean contains = (g_strv_length(separated_string) > 1);
g_strfreev(separated_string);
return contains;
}
GList * koto_utils_string_to_string_list(
gchar * s,
gchar * sep
) {
GList * list = NULL;
if (!koto_utils_is_string_valid(s)) { // Provided string is not valid
if (!koto_utils_string_is_valid(s)) { // Provided string is not valid
return list;
}
@ -189,17 +200,41 @@ GList * koto_utils_string_to_string_list(
for (guint i = 0; i < g_strv_length(separated_strings); i++) { // Iterate over each item
gchar * item = separated_strings[i];
list = g_list_append(list, g_strdup(item));
if (g_strcmp0(item, "") != 0) { // Not an empty string
list = g_list_append(list, g_strdup(item));
}
}
g_strfreev(separated_strings); // Free our strings
return list;
}
gchar * koto_utils_unquote_string(gchar * s) {
gchar * koto_utils_string_title(const gchar * s) {
if (!koto_utils_string_is_valid(s)) { // Not a valid string
return NULL;
}
glong len = g_utf8_strlen(s, -1);
if (len == 0) { // Empty string
return g_strdup(s); // Just duplicate itself
} else if (len == 1) { // One char
return g_utf8_strup(s, -1); // Uppercase all relevant cases
} else {
gchar * first_char = g_utf8_substring(s, 0, 1);
gchar * rest_of_string = g_utf8_substring(s, 1, len); // Rest of string
gchar * titled_string = g_strdup_printf("%s%s", g_utf8_strup(first_char, -1), rest_of_string);
g_free(first_char);
g_free(rest_of_string);
return titled_string;
}
}
gchar * koto_utils_string_unquote(gchar * s) {
gchar * new_s = NULL;
if (!koto_utils_is_string_valid(s)) { // Not a valid string
if (!koto_utils_string_is_valid(s)) { // Not a valid string
new_s = g_strdup("");
return new_s;
}

View file

@ -39,8 +39,6 @@ gchar * koto_utils_join_string_list(
gchar * sep
);
gboolean koto_utils_is_string_valid(gchar * str);
void koto_utils_mkdir(gchar * path);
void koto_utils_push_queue_element_to_store(
@ -48,22 +46,30 @@ void koto_utils_push_queue_element_to_store(
gpointer user_data
);
gchar * koto_utils_replace_string_all(
gchar * str,
gchar * find,
gchar * repl
);
gchar * koto_utils_seconds_to_time_format(guint64 seconds);
gboolean koto_utils_string_contains_substring(
gchar * s,
gchar * sub
);
gchar * koto_utils_string_get_valid(gchar * str);
gboolean koto_utils_string_is_valid(const gchar * str);
gchar * koto_utils_string_replace_all(
gchar * str,
gchar * find,
gchar * repl
);
GList * koto_utils_string_to_string_list(
gchar * s,
gchar * sep
);
gchar * koto_utils_unquote_string(gchar * s);
gchar * koto_utils_string_title(const gchar * s);
gchar * koto_utils_string_unquote(gchar * s);
G_END_DECLS

View file

@ -16,9 +16,10 @@
*/
#include <gtk-4.0/gdk/x11/gdkx.h>
#include "components/koto-action-bar.h"
#include "components/action-bar.h"
#include "db/cartographer.h"
#include "indexer/structs.h"
#include "pages/audiobooks/library.h"
#include "pages/music/music-local.h"
#include "pages/playlist/list.h"
#include "playback/engine.h"
@ -33,6 +34,7 @@
extern KotoActionBar * action_bar;
extern KotoAddRemoveTrackPopover * koto_add_remove_track_popup;
extern KotoAudiobooksLibraryPage * audiobooks_library_page;
extern KotoCartographer * koto_maps;
extern KotoCreateModifyPlaylistDialog * playlist_create_modify_dialog;
extern KotoConfig * config;
@ -138,11 +140,13 @@ static void koto_window_init (KotoWindow * self) {
gtk_widget_queue_draw(self->content_layout);
audiobooks_library_page = koto_audiobooks_library_page_new(); // Create our audiobooks library page
music_local_page = koto_page_music_local_new();
// TODO: Remove and do some fancy state loading
koto_window_add_page(self, "music.local", GTK_WIDGET(music_local_page));
koto_window_go_to_page(self, "music.local");
koto_window_add_page(self, "audiobooks.library", GTK_WIDGET(koto_audiobooks_library_page_get_main(audiobooks_library_page)));
koto_window_add_page(self, "music.library", GTK_WIDGET(music_local_page));
koto_window_go_to_page(self, "audiobooks.library");
gtk_widget_show(self->pages); // Do not remove this. Will cause sporadic hiding of the local page content otherwise.
}
@ -174,7 +178,7 @@ void koto_window_manage_style(
(void) prop_id;
if (!KOTO_IS_WINDOW(self)) { // Not a Koto Window
g_warning("Not a window");
return;
}
gchar * desired_theme = NULL;
@ -188,7 +192,7 @@ void koto_window_manage_style(
NULL
);
if (!koto_utils_is_string_valid(desired_theme)) { // Theme not valid
if (!koto_utils_string_is_valid(desired_theme)) { // Theme not valid
desired_theme = "dark";
}

View file

@ -1,6 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/com/github/joshstrobl/koto">
<file alias="business-and-personal-finance.png">../data/genres/business-and-personal-finance.png</file>
<file alias="foreign-languages.png">../data/genres/foreign-languages.png</file>
<file alias="mystery-and-thriller.png">../data/genres/mystery-and-thriller.png</file>
<file alias="sci-fi.png">../data/genres/sci-fi.png</file>
<file alias="travel.png">../data/genres/travel.png</file>
<file alias="koto-builtin-dark.css">../theme/koto-builtin-dark.css</file>
<file alias="koto-builtin-gruvbox.css">../theme/koto-builtin-gruvbox.css</file>
<file alias="koto-builtin-light.css">../theme/koto-builtin-light.css</file>

View file

@ -28,6 +28,7 @@
#include "playback/mimes.h"
#include "playback/mpris.h"
#include "paths.h"
#include "playlist/current.h"
#include "config/config.h"
#include "koto-paths.h"
@ -40,6 +41,7 @@ extern GDBusNodeInfo * introspection_data;
extern KotoPlaybackEngine * playback_engine;
extern KotoCartographer * koto_maps;
extern KotoCurrentPlaylist * current_playlist;
extern sqlite3 * koto_db;
extern GHashTable * supported_mimes_hash;
@ -74,6 +76,7 @@ static void on_activate (GtkApplication * app) {
static void on_shutdown(GtkApplication * app) {
(void) app;
koto_current_playlist_save_playlist_state(current_playlist); // Save the current playlist state if necessary before closure
koto_config_save(config); // Save our config
close_db(); // Close the database
g_bus_unown_name(mpris_bus_id);

View file

@ -5,8 +5,11 @@ add_global_arguments([
], language: 'c')
koto_sources = [
'components/koto-action-bar.c',
'components/koto-cover-art-button.c',
'components/album-info.c',
'components/action-bar.c',
'components/button.c',
'components/cover-art-button.c',
'components/track-item.c',
'components/track-table.c',
'config/config.c',
'db/cartographer.c',
@ -18,6 +21,11 @@ koto_sources = [
'indexer/library.c',
'indexer/track-helpers.c',
'indexer/track.c',
'pages/audiobooks/audiobook-view.c',
'pages/audiobooks/genres-banner.c',
'pages/audiobooks/genre-button.c',
'pages/audiobooks/library.c',
'pages/audiobooks/writer-page.c',
'pages/music/album-view.c',
'pages/music/artist-view.c',
'pages/music/disc-view.c',
@ -32,13 +40,11 @@ koto_sources = [
'playlist/current.c',
'playlist/playlist.c',
'main.c',
'koto-button.c',
'koto-dialog-container.c',
'koto-expander.c',
'koto-nav.c',
'koto-playerbar.c',
'koto-paths.c',
'koto-track-item.c',
'koto-utils.c',
'koto-window.c',
]

View file

@ -0,0 +1,255 @@
/* audiobook-view.c
*
* Copyright 2021 Joshua Strobl
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <glib-2.0/glib.h>
#include <gtk-4.0/gtk/gtk.h>
#include "../../components/album-info.h"
#include "../../components/button.h"
#include "../../components/track-item.h"
#include "../../db/cartographer.h"
#include "../../indexer/album-playlist-funcs.h"
#include "../../indexer/structs.h"
#include "../../playlist/current.h"
#include "../../koto-utils.h"
#include "../../koto-window.h"
#include "audiobook-view.h"
extern KotoCartographer * koto_maps;
extern KotoCurrentPlaylist * current_playlist;
extern KotoWindow * main_window;
struct _KotoAudiobookView {
GtkBox parent_instance;
KotoAlbum * album; // Album associated with this view
GtkWidget * side_info; // Our side info (artwork, playback button, position)
GtkWidget * info_contents; // Our info and contents vertical box
KotoAlbumInfo * album_info; // Our "Album" (Audiobook) info
GtkWidget * audiobook_art; // Our GtkImage for the audiobook art
GtkWidget * playback_button; // Our GtkButton for playback
GtkWidget * chapter_info; // Our GtkLabel of the position, effectively
GtkWidget * playback_position; // Our GtkLabel for the track playback position
GtkWidget * tracks_list; // GtkListBox of tracks
};
struct _KotoAudiobookViewClass {
GtkBoxClass parent_class;
};
G_DEFINE_TYPE(KotoAudiobookView, koto_audiobook_view, GTK_TYPE_BOX);
static void koto_audiobook_view_class_init(KotoAudiobookViewClass * c) {
(void) c;
}
static void koto_audiobook_view_init(KotoAudiobookView * self) {
gtk_widget_add_css_class(GTK_WIDGET(self), "audiobook-view");
self->side_info = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_widget_add_css_class(self->side_info, "side-info");
gtk_widget_set_size_request(self->side_info, 220, -1); // Match audiobook art size
self->info_contents = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
self->audiobook_art = gtk_image_new_from_icon_name("audio-x-generic-symbolic"); // Create an image with a symbolic icon
gtk_widget_set_size_request(self->audiobook_art, 220, 220); // Set to 220 like our cover art button
self->playback_button = gtk_button_new_with_label("Play"); // Default to our button saying Play instead of continue
gtk_widget_add_css_class(self->playback_button, "suggested-action");
g_signal_connect(self->playback_button, "clicked", G_CALLBACK(koto_audiobook_view_handle_play_clicked), self);
self->chapter_info = gtk_label_new(NULL);
self->playback_position = gtk_label_new(NULL);
gtk_widget_set_halign(self->chapter_info, GTK_ALIGN_START);
gtk_widget_set_halign(self->playback_position, GTK_ALIGN_START);
gtk_box_append(GTK_BOX(self->side_info), self->audiobook_art); // Add our audiobook art to the side info
gtk_box_append(GTK_BOX(self->side_info), GTK_WIDGET(self->playback_button)); // Add our playback button to the side info
gtk_box_append(GTK_BOX(self->side_info), self->chapter_info);
gtk_box_append(GTK_BOX(self->side_info), self->playback_position);
self->album_info = koto_album_info_new("audiobook"); // Create an "audiobook" type KotoAlbumInfo
gtk_box_append(GTK_BOX(self->info_contents), GTK_WIDGET(self->album_info)); // Add the album info the info contents
GtkWidget * chapters_label = gtk_label_new("Chapters");
gtk_widget_add_css_class(chapters_label, "chapters-label");
gtk_widget_set_halign(chapters_label, GTK_ALIGN_START); // Align to the start
gtk_box_append(GTK_BOX(self->info_contents), chapters_label);
self->tracks_list = gtk_list_box_new(); // Create our list of our tracks
gtk_list_box_set_activate_on_single_click(GTK_LIST_BOX(self->tracks_list), FALSE);
gtk_list_box_set_selection_mode(GTK_LIST_BOX(self->tracks_list), GTK_SELECTION_MULTIPLE);
gtk_widget_add_css_class(self->tracks_list, "track-list");
gtk_box_append(GTK_BOX(self->info_contents), self->tracks_list); // Add our listbox to the info contents
gtk_box_append(GTK_BOX(self), self->side_info); // Add our side info to the box
gtk_box_append(GTK_BOX(self), self->info_contents); // Add our info contents to the box
}
GtkWidget * koto_audiobook_view_create_track_item(
gpointer item,
gpointer user_data
) {
(void) user_data;
if (!KOTO_IS_TRACK(item)) { // Not actually a track
return NULL;
}
KotoTrack * track = KOTO_TRACK(item); // Cast our item as a track
KotoTrackItem * track_item = koto_track_item_new(track); // Create our track item
if (!KOTO_IS_TRACK_ITEM(track_item)) { // Not a track item
return NULL;
}
return GTK_WIDGET(track_item); // Cast as a widget and return the track item
}
void koto_audiobook_view_handle_play_clicked(
GtkButton * button,
gpointer user_data
) {
(void) button;
KotoAudiobookView * self = user_data;
if (!KOTO_IS_AUDIOBOOK_VIEW(self)) {
return;
}
if (!KOTO_IS_ALBUM(self->album)) { // Don't have an album associated with this view
return;
}
koto_playlist_commit(koto_album_get_playlist(self->album)); // Ensure we commit the current playlist to the database. This is needed to ensure we are able to save the playlist state going forward
koto_current_playlist_set_playlist(current_playlist, koto_album_get_playlist(self->album), TRUE, TRUE); // Set this playlist to be played immediately, play current track
}
void koto_audiobook_view_handle_playlist_updated(
KotoPlaylist * playlist,
gpointer user_data
) {
(void) playlist;
KotoAudiobookView * self = user_data;
if (!KOTO_IS_AUDIOBOOK_VIEW(self)) {
return;
}
koto_audiobook_view_update_side_info(self); // Update the side info based on the playlist modification
}
void koto_audiobook_view_set_album(
KotoAudiobookView * self,
KotoAlbum * album
) {
if (!KOTO_IS_AUDIOBOOK_VIEW(self)) {
return;
}
if (!KOTO_IS_ALBUM(album)) { // Not an album
return;
}
self->album = album;
gchar * album_art_path = koto_album_get_art(album); // Get any artwork
if (koto_utils_string_is_valid(album_art_path)) { // Have album art
gtk_image_set_from_file(GTK_IMAGE(self->audiobook_art), album_art_path); // Set our album art
}
koto_album_info_set_album_uuid(self->album_info, koto_album_get_uuid(album)); // Apply our album info
gtk_list_box_bind_model(
// Apply our binding for the GtkListBox
GTK_LIST_BOX(self->tracks_list),
G_LIST_MODEL(koto_album_get_store(album)),
koto_audiobook_view_create_track_item,
NULL,
koto_audiobook_view_destroy_associated_user_data
);
KotoPlaylist * album_playlist = koto_album_get_playlist(self->album); // Get the album playlist
if (!KOTO_IS_PLAYLIST(album_playlist)) { // Not a playlist
return;
}
g_signal_connect(album_playlist, "modified", G_CALLBACK(koto_audiobook_view_handle_playlist_updated), self); // Handle modifications of a playlist
g_signal_connect(album_playlist, "track-load-finalized", G_CALLBACK(koto_audiobook_view_handle_playlist_updated), self); // Handle when a playlist is finalized
koto_audiobook_view_update_side_info(self); // Update our side info
}
void koto_audiobook_view_update_side_info(KotoAudiobookView * self) {
if (!KOTO_IS_AUDIOBOOK_VIEW(self)) {
return;
}
if (!KOTO_IS_ALBUM(self->album)) { // Not an album
return;
}
KotoPlaylist * album_playlist = koto_album_get_playlist(self->album); // Get the album playlist
if (!KOTO_IS_PLAYLIST(album_playlist)) { // Not a playlist
return;
}
gint playlist_position = koto_playlist_get_current_position(album_playlist); // Get the current position in the playlist
gint playlist_length = koto_playlist_get_length(album_playlist); // Get the length of the playlist
KotoTrack * current_track = koto_playlist_get_current_track(album_playlist); // Get the track for the playlist
if (!KOTO_IS_TRACK(current_track)) { // Not a track
return;
}
guint playback_position = koto_track_get_playback_position(current_track);
guint duration = koto_track_get_duration(current_track);
gboolean continuing = (playlist_position >= 1) || ((playlist_position <= 0) && (playback_position != 0));
gtk_button_set_label(GTK_BUTTON(self->playback_button), continuing ? "Resume" : "Play");
if (continuing) { // Have been playing track
gtk_label_set_text(GTK_LABEL(self->chapter_info), g_strdup_printf("Track %i of %i", (playlist_position <= 0) ? 1 : (playlist_position + 1), playlist_length));
gtk_label_set_text(GTK_LABEL(self->playback_position), g_strdup_printf("%s / %s", koto_utils_seconds_to_time_format(playback_position), koto_utils_seconds_to_time_format(duration)));
gtk_widget_show(self->chapter_info);
gtk_widget_show(self->playback_position);
} else {
gtk_widget_hide(self->chapter_info); // Hide by default
gtk_widget_hide(self->playback_position); // Hide by default
}
}
void koto_audiobook_view_destroy_associated_user_data(gpointer user_data) {
(void) user_data;
}
KotoAudiobookView * koto_audiobook_view_new() {
return g_object_new(
KOTO_TYPE_AUDIOBOOK_VIEW,
"orientation",
GTK_ORIENTATION_HORIZONTAL,
NULL
);
}

View file

@ -0,0 +1,56 @@
/* audiobook-view.h
*
* Copyright 2021 Joshua Strobl
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <glib-2.0/glib-object.h>
#include <gtk-4.0/gtk/gtk.h>
#include "../../indexer/structs.h"
G_BEGIN_DECLS
#define KOTO_TYPE_AUDIOBOOK_VIEW (koto_audiobook_view_get_type())
G_DECLARE_FINAL_TYPE(KotoAudiobookView, koto_audiobook_view, KOTO, AUDIOBOOK_VIEW, GtkBox);
#define KOTO_IS_AUDIOBOOK_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_AUDIOBOOK_VIEW))
void koto_audiobook_view_set_album(
KotoAudiobookView * self,
KotoAlbum * album
);
GtkWidget * koto_audiobook_view_create_track_item(
gpointer item,
gpointer user_data
);
void koto_audiobook_view_handle_play_clicked(
GtkButton * button,
gpointer user_data
);
void koto_audiobook_view_handle_playlist_updated(
KotoPlaylist * playlist,
gpointer user_data
);
void koto_audiobook_view_update_side_info(KotoAudiobookView * self);
void koto_audiobook_view_destroy_associated_user_data(gpointer user_data);
KotoAudiobookView * koto_audiobook_view_new();
G_END_DECLS

View file

@ -0,0 +1,216 @@
/* genres-button.c
*
* Copyright 2021 Joshua Strobl
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <glib-2.0/glib.h>
#include <gtk-4.0/gtk/gtk.h>
#include "../../components/button.h"
#include "../../koto-utils.h"
#include "genre-button.h"
enum {
PROP_0,
PROP_GENRE_ID,
PROP_GENRE_NAME,
N_PROPS
};
static GParamSpec * genre_button_props[N_PROPS] = {
NULL,
};
struct _KotoAudiobooksGenreButton {
GtkBox parent_instance;
gchar * genre_id;
gchar * genre_name;
GtkWidget * bg_image;
KotoButton * button;
};
struct _KotoAudiobooksGenreButtonClass {
GtkBoxClass parent_class;
};
G_DEFINE_TYPE(KotoAudiobooksGenreButton, koto_audiobooks_genre_button, GTK_TYPE_BOX);
static void koto_audiobooks_genre_button_get_property(
GObject * obj,
guint prop_id,
GValue * val,
GParamSpec * spec
);
static void koto_audiobooks_genre_button_set_property(
GObject * obj,
guint prop_id,
const GValue * val,
GParamSpec * spec
);
static void koto_audiobooks_genre_button_class_init(KotoAudiobooksGenreButtonClass * c) {
GObjectClass * gobject_class;
gobject_class = G_OBJECT_CLASS(c);
gobject_class->set_property = koto_audiobooks_genre_button_set_property;
gobject_class->get_property = koto_audiobooks_genre_button_get_property;
genre_button_props[PROP_GENRE_ID] = g_param_spec_string(
"genre-id",
"Genre ID",
"Genre ID",
NULL,
G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
);
genre_button_props[PROP_GENRE_NAME] = g_param_spec_string(
"genre-name",
"Genre Name",
"Genre Name",
NULL,
G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
);
g_object_class_install_properties(gobject_class, N_PROPS, genre_button_props);
}
static void koto_audiobooks_genre_button_init(KotoAudiobooksGenreButton * self) {
gtk_widget_add_css_class(GTK_WIDGET(self), "audiobook-genre-button");
gtk_widget_set_hexpand(GTK_WIDGET(self), FALSE);
gtk_widget_set_vexpand(GTK_WIDGET(self), FALSE);
gtk_widget_set_size_request(GTK_WIDGET(self), 260, 120);
GtkWidget * overlay = gtk_overlay_new(); // Create our overlay
self->bg_image = gtk_picture_new(); // Create our new image
self->button = koto_button_new_plain(NULL); // Create a new button
koto_button_set_text_wrap(self->button, TRUE); // Enable text wrapping for long genre lines
gtk_widget_set_valign(GTK_WIDGET(self->button), GTK_ALIGN_END); // Align towards bottom of button
gtk_widget_set_hexpand(overlay, TRUE);
gtk_overlay_set_child(GTK_OVERLAY(overlay), self->bg_image);
gtk_overlay_add_overlay(GTK_OVERLAY(overlay), GTK_WIDGET(self->button));
gtk_box_append(GTK_BOX(self), overlay); // Add the overlay to self
}
static void koto_audiobooks_genre_button_get_property(
GObject * obj,
guint prop_id,
GValue * val,
GParamSpec * spec
) {
KotoAudiobooksGenreButton * self = KOTO_AUDIOBOOKS_GENRE_BUTTON(obj);
switch (prop_id) {
case PROP_GENRE_ID:
g_value_set_string(val, self->genre_id);
break;
case PROP_GENRE_NAME:
g_value_set_string(val, self->genre_name);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
break;
}
}
static void koto_audiobooks_genre_button_set_property(
GObject * obj,
guint prop_id,
const GValue * val,
GParamSpec * spec
) {
KotoAudiobooksGenreButton * self = KOTO_AUDIOBOOKS_GENRE_BUTTON(obj);
switch (prop_id) {
case PROP_GENRE_ID:
koto_audiobooks_genre_button_set_id(self, g_strdup(g_value_get_string(val)));
break;
case PROP_GENRE_NAME:
koto_audiobooks_genre_button_set_name(self, g_strdup(g_value_get_string(val)));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
break;
}
}
KotoButton * koto_audiobooks_genre_button_get_button(KotoAudiobooksGenreButton * self) {
return KOTO_IS_AUDIOBOOKS_GENRE_BUTTON(self) ? self->button : NULL;
}
gchar * koto_audiobooks_genre_button_get_id(KotoAudiobooksGenreButton * self) {
return KOTO_IS_AUDIOBOOKS_GENRE_BUTTON(self) ? self->genre_id : NULL;
}
gchar * koto_audiobooks_genre_button_get_name(KotoAudiobooksGenreButton * self) {
return KOTO_IS_AUDIOBOOKS_GENRE_BUTTON(self) ? self->genre_name : NULL;
}
void koto_audiobooks_genre_button_set_id(
KotoAudiobooksGenreButton * self,
gchar * genre_id
) {
if (!KOTO_IS_AUDIOBOOKS_GENRE_BUTTON(self)) {
return;
}
if (!koto_utils_string_is_valid(genre_id)) {
return;
}
self->genre_id = genre_id;
gtk_picture_set_resource(GTK_PICTURE(self->bg_image), g_strdup_printf("/com/github/joshstrobl/koto/%s.png", genre_id));
gtk_widget_set_size_request(self->bg_image, 260, 120);
}
void koto_audiobooks_genre_button_set_name(
KotoAudiobooksGenreButton * self,
gchar * genre_name
) {
if (!KOTO_IS_AUDIOBOOKS_GENRE_BUTTON(self)) {
return;
}
if (!koto_utils_string_is_valid(genre_name)) {
return;
}
if (koto_utils_string_is_valid(self->genre_name)) { // Already have a genre name
g_free(self->genre_name);
}
self->genre_name = genre_name;
koto_button_set_text(self->button, genre_name);
}
KotoAudiobooksGenreButton * koto_audiobooks_genre_button_new(
gchar * genre_id,
gchar * genre_name
) {
return g_object_new(
KOTO_TYPE_AUDIOBOOKS_GENRE_BUTTON,
"genre-id",
genre_id,
"genre-name",
genre_name,
NULL
);
}

View file

@ -0,0 +1,49 @@
/* genres-button.h
*
* Copyright 2021 Joshua Strobl
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <glib-2.0/glib.h>
#include <gtk-4.0/gtk/gtk.h>
#include "../../components/button.h"
G_BEGIN_DECLS
#define KOTO_TYPE_AUDIOBOOKS_GENRE_BUTTON koto_audiobooks_genre_button_get_type()
G_DECLARE_FINAL_TYPE(KotoAudiobooksGenreButton, koto_audiobooks_genre_button, KOTO, AUDIOBOOKS_GENRE_BUTTON, GtkBox)
#define KOTO_IS_AUDIOBOOKS_GENRE_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_AUDIOBOOKS_GENRE_BUTTON))
KotoButton * koto_audiobooks_genre_button_get_button(KotoAudiobooksGenreButton * self);
gchar * koto_audiobooks_genre_button_get_id(KotoAudiobooksGenreButton * self);
gchar * koto_audiobooks_genre_button_get_name(KotoAudiobooksGenreButton * self);
void koto_audiobooks_genre_button_set_id(
KotoAudiobooksGenreButton * self,
gchar * genre_id
);
void koto_audiobooks_genre_button_set_name(
KotoAudiobooksGenreButton * self,
gchar * genre_name
);
KotoAudiobooksGenreButton * koto_audiobooks_genre_button_new(
gchar * genre_id,
gchar * genre_name
);
G_END_DECLS

View file

@ -0,0 +1,153 @@
/* genres-banner.c
*
* Copyright 2021 Joshua Strobl
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <glib-2.0/glib.h>
#include <gtk-4.0/gtk/gtk.h>
#include "../../components/button.h"
#include "../../koto-utils.h"
#include "genre-button.h"
#include "genres-banner.h"
enum {
SIGNAL_GENRE_CLICKED,
N_SIGNALS
};
static guint banner_signals[N_SIGNALS] = {
0
};
struct _KotoAudiobooksGenresBanner {
GObject parent_instance;
GtkWidget * main; // Our main content
GtkWidget * banner_revealer; // Our GtkRevealer for the strip
GtkWidget * banner_viewport; // Our banner viewport
GtkWidget * banner_content; // Our banner content as a horizontal box
GtkWidget * strip_revealer; // Our GtkRevealer for the strip
GtkWidget * strip_viewport; // Our strip viewport
GtkWidget * strip_content; // Our strip content
GHashTable * genre_ids_to_names; // Our genre "ids" to the name
GHashTable * genre_ids_to_ptrs; // Our HashTable of genre IDs to our GPtrArray with pointers to banner and strip buttons
};
struct _KotoAudiobooksGenresBannerClass {
GObjectClass parent_instance;
void (* genre_clicked) (gchar * genre);
};
G_DEFINE_TYPE(KotoAudiobooksGenresBanner, koto_audiobooks_genres_banner, G_TYPE_OBJECT);
static void koto_audiobooks_genres_banner_class_init(KotoAudiobooksGenresBannerClass * c) {
GObjectClass * gobject_class = G_OBJECT_CLASS(c);
banner_signals[SIGNAL_GENRE_CLICKED] = g_signal_new(
"genre-clicked",
G_TYPE_FROM_CLASS(gobject_class),
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET(KotoAudiobooksGenresBannerClass, genre_clicked),
NULL,
NULL,
NULL,
G_TYPE_NONE,
1,
G_TYPE_CHAR
);
}
static void koto_audiobooks_genres_banner_init(KotoAudiobooksGenresBanner * self) {
self->genre_ids_to_ptrs = g_hash_table_new(g_str_hash, g_str_equal);
self->genre_ids_to_names = g_hash_table_new(g_str_hash, g_str_equal);
g_hash_table_insert(self->genre_ids_to_names, g_strdup("hip-hop"), g_strdup("Hip-hop"));
g_hash_table_insert(self->genre_ids_to_names, g_strdup("indie"), g_strdup("Indie"));
g_hash_table_insert(self->genre_ids_to_names, g_strdup("sci-fi"), g_strdup("Science Fiction"));
self->main = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); // Create the main box as a vertical box
gtk_widget_add_css_class(self->main, "genres-banner");
gtk_widget_set_halign(self->main, GTK_ALIGN_START);
gtk_widget_set_vexpand(self->main, FALSE);
self->banner_revealer = gtk_revealer_new(); // Create our revealer for the banner
self->strip_revealer = gtk_revealer_new(); // Create a revealer for the strip
gtk_revealer_set_transition_type(GTK_REVEALER(self->strip_revealer), GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN);
gtk_revealer_set_transition_type(GTK_REVEALER(self->banner_revealer), GTK_REVEALER_TRANSITION_TYPE_SLIDE_UP);
self->banner_viewport = gtk_viewport_new(NULL, NULL); // Create our viewport for enabling the banner content to be scrollable
self->strip_viewport = gtk_viewport_new(NULL, NULL); // Create our viewport for enabling the strip content to be scrollable
self->banner_content = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); // Create our horizontal box for the content
gtk_widget_add_css_class(self->banner_content, "large-banner");
self->strip_content = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); // Create the horizontal box for the strip content
gtk_widget_add_css_class(self->strip_content, "strip");
gtk_revealer_set_child(GTK_REVEALER(self->banner_revealer), self->banner_viewport); // Set the viewport to be the content which gets revealed
gtk_revealer_set_child(GTK_REVEALER(self->strip_revealer), self->strip_viewport); // Set the viewport to be the cont ent which gets revealed
gtk_revealer_set_reveal_child(GTK_REVEALER(self->banner_revealer), TRUE); // Show the banner by default
gtk_viewport_set_child(GTK_VIEWPORT(self->banner_viewport), self->banner_content); // Set the banner content for this viewport
gtk_viewport_set_child(GTK_VIEWPORT(self->strip_viewport), self->strip_content); // Set the strip content for this viewport
gtk_box_append(GTK_BOX(self->main), self->strip_revealer);
gtk_box_append(GTK_BOX(self->main), self->banner_revealer);
}
void koto_audiobooks_genres_banner_add_genre(
KotoAudiobooksGenresBanner * self,
gchar * genre_id
) {
if (!KOTO_IS_AUDIOBOOKS_GENRES_BANNER(self)) { // Not a banner
return;
}
if (!koto_utils_string_is_valid(genre_id)) { // Not a valid genre ID
return;
}
if (g_hash_table_contains(self->genre_ids_to_ptrs, genre_id)) { // Already have added this
return;
}
gchar * name = g_hash_table_lookup(self->genre_ids_to_names, genre_id); // Get the named equivelant for this ID
if (name == NULL) { // Didn't find a name equivelant
name = g_strdup(genre_id); // Just duplicate the genre ID for now
}
KotoButton * genre_strip_button = koto_button_new_plain(name); // Create a new button with the name
gtk_box_append(GTK_BOX(self->strip_content), GTK_WIDGET(genre_strip_button)); // Add our KotoButton to the strip content
KotoAudiobooksGenreButton * genre_banner_button = koto_audiobooks_genre_button_new(genre_id, name); // Create our big button
gtk_box_append(GTK_BOX(self->banner_content), GTK_WIDGET(genre_banner_button));
GPtrArray * buttons = g_ptr_array_new();
g_ptr_array_add(buttons, (gpointer) genre_strip_button); // Add our KotoButton as a gpointer to our GPtrArray as the first item
g_ptr_array_add(buttons, (gpointer) genre_banner_button); // Add our KotoAudiobooksGenresButton as a gpointer to our GPtrArray as the second item
g_hash_table_replace(self->genre_ids_to_ptrs, genre_id, buttons); // Add our GPtrArray to our genre_ids_to_ptrs
}
GtkWidget * koto_audiobooks_genres_banner_get_main(KotoAudiobooksGenresBanner * self) {
return KOTO_IS_AUDIOBOOKS_GENRES_BANNER(self) ? self->main : NULL;
}
KotoAudiobooksGenresBanner * koto_audiobooks_genres_banner_new() {
return g_object_new(KOTO_TYPE_AUDIOBOOKS_GENRE_BANNER, NULL);
}

View file

@ -0,0 +1,44 @@
/* genres-banner.h
*
* Copyright 2021 Joshua Strobl
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <glib-2.0/glib-object.h>
#include <gtk-4.0/gtk/gtk.h>
G_BEGIN_DECLS
#define KOTO_TYPE_AUDIOBOOKS_GENRE_BANNER koto_audiobooks_genres_banner_get_type()
#define KOTO_AUDIOBOOKS_GENRES_BANNER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), KOTO_TYPE_AUDIOBOOKS_GENRE_BANNER, KotoAudiobooksGenresBanner))
typedef struct _KotoAudiobooksGenresBanner KotoAudiobooksGenresBanner;
typedef struct _KotoAudiobooksGenresBannerClass KotoAudiobooksGenresBannerClass;
GLIB_AVAILABLE_IN_ALL
GType koto_audiobooks_genres_banner_get_type(void) G_GNUC_CONST;
#define KOTO_IS_AUDIOBOOKS_GENRES_BANNER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_AUDIOBOOKS_GENRE_BANNER))
void koto_audiobooks_genres_banner_add_genre(
KotoAudiobooksGenresBanner * self,
gchar * genre_id
);
GtkWidget * koto_audiobooks_genres_banner_get_main(KotoAudiobooksGenresBanner * self);
KotoAudiobooksGenresBanner * koto_audiobooks_genres_banner_new();
G_END_DECLS

View file

@ -0,0 +1,213 @@
/* library.c
*
* Copyright 2021 Joshua Strobl
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <glib-2.0/glib.h>
#include <gtk-4.0/gtk/gtk.h>
#include "../../components/button.h"
#include "../../db/cartographer.h"
#include "../../indexer/structs.h"
#include "../../koto-utils.h"
#include "../../koto-window.h"
#include "genres-banner.h"
#include "library.h"
#include "writer-page.h"
extern KotoCartographer * koto_maps;
extern KotoWindow * main_window;
struct _KotoAudiobooksLibraryPage {
GObject parent_instance;
GtkWidget * main; // Our main content, contains banner and scrolled window
GtkWidget * content_scroll; // Our Scrolled Window
GtkWidget * content; // Content inside scrolled window
KotoAudiobooksGenresBanner * banner;
GtkWidget * writers_flow;
GHashTable * writers_to_buttons; // HashTable of UUIDs of "Artists" to their KotoButton
GHashTable * writers_to_pages; // HashTable of UUIDs of "Artists" to their KotoAudiobooksWritersPage
};
struct _KotoAudiobooksLibraryPageClass {
GObjectClass parent_instance;
};
G_DEFINE_TYPE(KotoAudiobooksLibraryPage, koto_audiobooks_library_page, G_TYPE_OBJECT);
KotoAudiobooksLibraryPage * audiobooks_library_page;
static void koto_audiobooks_library_page_init(KotoAudiobooksLibraryPage * self) {
self->main = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_widget_add_css_class(self->main, "audiobook-library");
self->content_scroll = gtk_scrolled_window_new(); // Create our GtkScrolledWindow
self->content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_widget_set_vexpand(self->content, TRUE); // Ensure content expands vertically to allow flowboxchild to take up as much space as it requests
gtk_widget_add_css_class(self->content, "content");
self->banner = koto_audiobooks_genres_banner_new(); // Create our banner
self->writers_flow = gtk_flow_box_new(); // Create our flow box
//gtk_flow_box_set_homogeneous(GTK_FLOW_BOX(self->writers_flow), TRUE);
gtk_flow_box_set_max_children_per_line(GTK_FLOW_BOX(self->writers_flow), 100); // Set to a random amount that is not realistic, however GTK sets a default to 7 which is too small.
gtk_flow_box_set_selection_mode(GTK_FLOW_BOX(self->writers_flow), GTK_SELECTION_NONE);
gtk_widget_add_css_class(self->writers_flow, "writers-button-flow");
self->writers_to_buttons = g_hash_table_new(g_str_hash, g_str_equal);
self->writers_to_pages = g_hash_table_new(g_str_hash, g_str_equal);
g_signal_connect(koto_maps, "album-added", G_CALLBACK(koto_audiobooks_library_page_handle_add_album), self); // Notify when we have a new Album
g_signal_connect(koto_maps, "artist-added", G_CALLBACK(koto_audiobooks_library_page_handle_add_artist), self); // Notify when we have a new Artist
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(self->content_scroll), self->content); // Add our content to the content scroll
gtk_box_append(GTK_BOX(self->main), koto_audiobooks_genres_banner_get_main(self->banner)); // Add the banner to the content
gtk_box_append(GTK_BOX(self->main), self->content_scroll); // Add our scroll window to the main content
gtk_box_append(GTK_BOX(self->content), self->writers_flow); // Add our flowbox to the content
}
static void koto_audiobooks_library_page_class_init(KotoAudiobooksLibraryPageClass * c) {
(void) c;
}
void koto_audiobooks_library_page_create_artist_ux(
KotoAudiobooksLibraryPage * self,
KotoArtist * artist
) {
if (!KOTO_IS_AUDIOBOOKS_LIBRARY_PAGE(self)) { // Not a AudiobooksLibraryPage
return;
}
if (!KOTO_IS_ARTIST(artist)) { // Not an artist
return;
}
gchar * artist_uuid = koto_artist_get_uuid(artist); // Get the artist UUID
if (!g_hash_table_contains(self->writers_to_pages, artist_uuid)) { // Don't have the page
KotoWriterPage * writers_page = koto_writer_page_new(artist);
koto_window_add_page(main_window, artist_uuid, koto_writer_page_get_main(writers_page)); // Add the page to the stack
}
if (!g_hash_table_contains(self->writers_to_buttons, artist_uuid)) { // Don't have the button for this
KotoButton * artist_button = koto_button_new_plain(koto_artist_get_name(artist)); // Create a KotoButton for this artist
koto_button_set_data(artist_button, artist_uuid); // Set the artist_uuid as the data for the button
koto_button_set_text_justify(artist_button, GTK_JUSTIFY_CENTER); // Center the text
koto_button_set_text_wrap(artist_button, TRUE);
gtk_widget_add_css_class(GTK_WIDGET(artist_button), "writer-button");
gtk_widget_set_size_request(GTK_WIDGET(artist_button), 260, 120);
koto_button_add_click_handler(artist_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_button_global_page_nav_callback), artist_button);
g_hash_table_replace(self->writers_to_buttons, artist_uuid, artist_button); // Add the button to the Writers to Buttons hashtable
gtk_flow_box_insert(GTK_FLOW_BOX(self->writers_flow), GTK_WIDGET(artist_button), -1); // Append the button to our flowbox
GtkWidget * button_parent = gtk_widget_get_parent(GTK_WIDGET(artist_button)); // This is the GtkFlowboxChild that the button is in
gtk_widget_set_halign(button_parent, GTK_ALIGN_START);
}
}
void koto_audiobooks_library_page_handle_add_album(
KotoCartographer * carto,
KotoAlbum * album,
KotoAudiobooksLibraryPage * self
) {
if (!KOTO_IS_CARTOGRAPHER(carto)) { // Not cartographer
return;
}
if (!KOTO_IS_ALBUM(album)) { // Not an album
return;
}
if (!KOTO_IS_AUDIOBOOKS_LIBRARY_PAGE(self)) { // Not a AudiobooksLibraryPage
return;
}
gchar * artist_uuid = koto_album_get_artist_uuid(album); // Get the Album's artist UUID
KotoArtist * artist = koto_cartographer_get_artist_by_uuid(koto_maps, artist_uuid);
if (!KOTO_IS_ARTIST(artist)) { // Failed to get artist
return;
}
if (koto_artist_get_lib_type(artist) != KOTO_LIBRARY_TYPE_AUDIOBOOK) { // Not in an Audiobook library
return;
}
koto_audiobooks_library_page_add_genres(self, koto_album_get_genres(album)); // Add all the genres necessary for this album
}
void koto_audiobooks_library_page_handle_add_artist(
KotoCartographer * carto,
KotoArtist * artist,
KotoAudiobooksLibraryPage * self
) {
if (!KOTO_IS_CARTOGRAPHER(carto)) { // Not cartographer
return;
}
if (!KOTO_IS_ARTIST(artist)) { // Not an artist
return;
}
if (!KOTO_IS_AUDIOBOOKS_LIBRARY_PAGE(self)) { // Not a AudiobooksLibraryPage
return;
}
if (koto_artist_get_lib_type(artist) != KOTO_LIBRARY_TYPE_AUDIOBOOK) { // Not in an Audiobook library
return;
}
koto_audiobooks_library_page_create_artist_ux(self, artist); // Create the UX for the artist if necessary
}
void koto_audiobooks_library_page_add_genres(
KotoAudiobooksLibraryPage * self,
GList * genres
) {
if (!KOTO_IS_AUDIOBOOKS_LIBRARY_PAGE(self)) { // Not a AudiobooksLibraryPage
return;
}
if (g_list_length(genres) == 0) { // No contents
return;
}
GList * cur_list;
for (cur_list = genres; cur_list != NULL; cur_list = cur_list->next) { // Iterate over each genre
gchar * genre = (gchar*) cur_list->data;
if (!koto_utils_string_is_valid(genre)) {
continue;
}
if (g_strcmp0(genre, "audiobook") == 0) { // Is generic
continue;
}
koto_audiobooks_genres_banner_add_genre(self->banner, genre); // Add this genre
}
}
GtkWidget * koto_audiobooks_library_page_get_main(KotoAudiobooksLibraryPage * self) {
return KOTO_IS_AUDIOBOOKS_LIBRARY_PAGE(self) ? self->main : NULL;
}
KotoAudiobooksLibraryPage * koto_audiobooks_library_page_new() {
return g_object_new(KOTO_TYPE_AUDIOBOOKS_LIBRARY_PAGE, NULL);
}

View file

@ -0,0 +1,56 @@
/* library.h
*
* Copyright 2021 Joshua Strobl
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <glib-2.0/glib-object.h>
#include <gtk-4.0/gtk/gtk.h>
#include "../../db/cartographer.h"
#include "../../indexer/structs.h"
G_BEGIN_DECLS
#define KOTO_TYPE_AUDIOBOOKS_LIBRARY_PAGE koto_audiobooks_library_page_get_type()
#define KOTO_AUDIOBOOKS_LIBRARY_PAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), KOTO_TYPE_AUDIOBOOKS_LIBRARY_PAGE, KotoAudiobooksLibraryPage))
typedef struct _KotoAudiobooksLibraryPage KotoAudiobooksLibraryPage;
typedef struct _KotoAudiobooksLibraryPageClass KotoAudiobooksLibraryPageClass;
GLIB_AVAILABLE_IN_ALL
GType koto_audiobooks_library_page_get_type(void) G_GNUC_CONST;
#define KOTO_IS_AUDIOBOOKS_LIBRARY_PAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_AUDIOBOOKS_LIBRARY_PAGE))
void koto_audiobooks_library_page_add_genres(
KotoAudiobooksLibraryPage * self,
GList * genres
);
void koto_audiobooks_library_page_handle_add_album(
KotoCartographer * carto,
KotoAlbum * album,
KotoAudiobooksLibraryPage * self
);
void koto_audiobooks_library_page_handle_add_artist(
KotoCartographer * carto,
KotoArtist * artist,
KotoAudiobooksLibraryPage * self
);
GtkWidget * koto_audiobooks_library_page_get_main(KotoAudiobooksLibraryPage * self);
KotoAudiobooksLibraryPage * koto_audiobooks_library_page_new();

View file

@ -0,0 +1,206 @@
/* writers-page.c
*
* Copyright 2021 Joshua Strobl
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <glib-2.0/glib.h>
#include <gtk-4.0/gtk/gtk.h>
#include "../../db/cartographer.h"
#include "../../indexer/structs.h"
#include "../../koto-utils.h"
#include "../../koto-window.h"
#include "audiobook-view.h"
#include "writer-page.h"
extern KotoCartographer * koto_maps;
extern KotoWindow * main_window;
enum {
PROP_0,
PROP_ARTIST,
N_PROPERTIES
};
static GParamSpec * props[N_PROPERTIES] = {
NULL,
};
struct _KotoWriterPage {
GObject parent_instance;
KotoArtist * artist;
GtkWidget * main; // Our main content will be a scrolled Scrolled Window
GtkWidget * content; // Content inside scrolled window
GtkWidget * writers_header; // Header GtkLabel for the writer
GListModel * model;
GtkWidget * audiobooks_flow;
};
struct _KotoWriterPageClass {
GObjectClass parent_class;
};
G_DEFINE_TYPE(KotoWriterPage, koto_writer_page, G_TYPE_OBJECT);
static void koto_writer_page_get_property(
GObject * obj,
guint prop_id,
GValue * val,
GParamSpec * spec
);
static void koto_writer_page_set_property(
GObject * obj,
guint prop_id,
const GValue * val,
GParamSpec * spec
);
static void koto_writer_page_class_init(KotoWriterPageClass * c) {
GObjectClass * gobject_class;
gobject_class = G_OBJECT_CLASS(c);
gobject_class->set_property = koto_writer_page_set_property;
gobject_class->get_property = koto_writer_page_get_property;
props[PROP_ARTIST] = g_param_spec_object(
"artist",
"Artist",
"Artist",
KOTO_TYPE_ARTIST,
G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
);
g_object_class_install_properties(gobject_class, N_PROPERTIES, props);
}
static void koto_writer_page_init(KotoWriterPage * self) {
self->main = gtk_scrolled_window_new(); // Set main to be a scrolled window
self->content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_widget_add_css_class(self->content, "writer-page");
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(self->main), self->content); // Set content to be the child of the scrolled window
self->writers_header = gtk_label_new(NULL); // Create an empty label
gtk_widget_add_css_class(self->writers_header, "writer-header");
gtk_widget_set_halign(self->writers_header, GTK_ALIGN_START);
self->audiobooks_flow = gtk_flow_box_new(); // Create our flowbox of the audiobooks views
gtk_widget_add_css_class(self->audiobooks_flow, "audiobooks-flow");
gtk_flow_box_set_max_children_per_line(GTK_FLOW_BOX(self->audiobooks_flow), 2); // Allow 2 to ensure adequate spacing for description
gtk_flow_box_set_selection_mode(GTK_FLOW_BOX(self->audiobooks_flow), GTK_SELECTION_NONE); // Do not allow selection
gtk_widget_set_hexpand(self->audiobooks_flow, TRUE); // Expand horizontally
gtk_widget_set_vexpand(self->audiobooks_flow, TRUE); // Expand vertically
gtk_box_append(GTK_BOX(self->content), self->writers_header);
gtk_box_append(GTK_BOX(self->content), self->audiobooks_flow); // Add the audiobooks flow to the content
}
static void koto_writer_page_get_property(
GObject * obj,
guint prop_id,
GValue * val,
GParamSpec * spec
) {
KotoWriterPage * self = KOTO_WRITER_PAGE(obj);
switch (prop_id) {
case PROP_ARTIST:
g_value_set_object(val, self->artist);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
break;
}
}
static void koto_writer_page_set_property(
GObject * obj,
guint prop_id,
const GValue * val,
GParamSpec * spec
) {
KotoWriterPage * self = KOTO_WRITER_PAGE(obj);
switch (prop_id) {
case PROP_ARTIST:
koto_writer_page_set_artist(self, (KotoArtist*) g_value_get_object(val));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
break;
}
}
GtkWidget * koto_writer_page_create_item(
gpointer item,
gpointer user_data
) {
(void) user_data;
KotoAlbum * album = KOTO_ALBUM(item); // Cast the model data as an album
if (!KOTO_IS_ALBUM(album)) { // Fetched item from list is not album
return NULL;
}
KotoAudiobookView * view = koto_audiobook_view_new(); // Create our KotoAudiobookView
koto_audiobook_view_set_album(view, album); // Set the item for this set up audiobook view
return GTK_WIDGET(view);
}
GtkWidget * koto_writer_page_get_main(KotoWriterPage * self) {
return self->main;
}
void koto_writer_page_set_artist(
KotoWriterPage * self,
KotoArtist * artist
) {
if (!KOTO_IS_WRITER_PAGE(self)) { // Not a writers page
return;
}
if (!KOTO_IS_ARTIST(artist)) { // Not an artist
return;
}
self->artist = artist;
gtk_label_set_text(GTK_LABEL(self->writers_header), koto_artist_get_name(self->artist)); // Get the label for the writers header
self->model = G_LIST_MODEL(koto_artist_get_albums_store(artist)); // Get the store and cast it as a list model for our model
gtk_flow_box_bind_model(
GTK_FLOW_BOX(self->audiobooks_flow),
self->model,
koto_writer_page_create_item,
NULL,
NULL
);
}
KotoWriterPage * koto_writer_page_new(KotoArtist * artist) {
return g_object_new(
KOTO_TYPE_WRITER_PAGE,
"artist",
artist,
NULL
);
}

View file

@ -0,0 +1,41 @@
/* writers-page.h
*
* Copyright 2021 Joshua Strobl
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <glib-2.0/glib.h>
#include <gtk-4.0/gtk/gtk.h>
G_BEGIN_DECLS
#define KOTO_TYPE_WRITER_PAGE (koto_writer_page_get_type())
G_DECLARE_FINAL_TYPE(KotoWriterPage, koto_writer_page, KOTO, WRITER_PAGE, GObject)
GtkWidget* koto_writer_page_create_item(
gpointer item,
gpointer user_data
);
GtkWidget * koto_writer_page_get_main(KotoWriterPage * self);
void koto_writer_page_set_artist(
KotoWriterPage * self,
KotoArtist * artist
);
KotoWriterPage * koto_writer_page_new(KotoArtist * artist);
G_END_DECLS

View file

@ -17,10 +17,11 @@
#include <glib-2.0/glib.h>
#include <gtk-4.0/gtk/gtk.h>
#include "../../components/koto-cover-art-button.h"
#include "../../components/album-info.h"
#include "../../components/button.h"
#include "../../components/cover-art-button.h"
#include "../../db/cartographer.h"
#include "../../indexer/structs.h"
#include "../../koto-button.h"
#include "album-view.h"
#include "disc-view.h"
#include "config/config.h"
@ -37,7 +38,7 @@ struct _KotoAlbumView {
KotoCoverArtButton * album_cover;
GtkWidget * album_label;
KotoAlbumInfo * album_info;
GHashTable * cd_to_disc_views;
};
@ -90,6 +91,8 @@ static void koto_album_view_init(KotoAlbumView * self) {
gtk_widget_add_css_class(self->main, "album-view");
gtk_widget_set_can_focus(self->main, FALSE);
self->album_tracks_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
self->album_info = koto_album_info_new(KOTO_ALBUM_INFO_TYPE_ALBUM); // Create an Album-type Album Info
gtk_box_prepend(GTK_BOX(self->album_tracks_box), GTK_WIDGET(self->album_info)); // Add our Album Info to the album_tracks box
self->discs = gtk_list_box_new(); // Create our list of our tracks
gtk_list_box_set_selection_mode(GTK_LIST_BOX(self->discs), GTK_SELECTION_NONE);
@ -228,11 +231,7 @@ void koto_album_view_set_album(
gchar * album_art = koto_album_get_art(self->album); // Get the art for the album
koto_cover_art_button_set_art_path(self->album_cover, album_art);
self->album_label = gtk_label_new(koto_album_get_name(album));
gtk_widget_set_halign(self->album_label, GTK_ALIGN_START);
gtk_box_prepend(GTK_BOX(self->album_tracks_box), self->album_label); // Prepend our new label to the album + tracks box
koto_album_info_set_album_uuid(self->album_info, koto_album_get_uuid(album)); // Set the album we should be displaying info about on the Album Info
g_signal_connect(self->album, "track-added", G_CALLBACK(koto_album_view_handle_track_added), self); // Handle track added on our album
}

View file

@ -17,7 +17,7 @@
#include <glib-2.0/glib.h>
#include <gtk-4.0/gtk/gtk.h>
#include "../../components/koto-cover-art-button.h"
#include "../../components/cover-art-button.h"
#include "../../components/track-table.h"
#include "../../db/cartographer.h"
#include "../../indexer/artist-playlist-funcs.h"
@ -323,7 +323,7 @@ void koto_artist_view_toggle_playback(
return;
}
koto_current_playlist_set_playlist(current_playlist, artist_playlist, TRUE); // Set our playlist to the one associated with the Artist and start playback immediately
koto_current_playlist_set_playlist(current_playlist, artist_playlist, TRUE, FALSE); // Set our playlist to the one associated with the Artist and start playback immediately
}
KotoArtistView * koto_artist_view_new(KotoArtist * artist) {

View file

@ -16,11 +16,11 @@
*/
#include <gtk-4.0/gtk/gtk.h>
#include "../../components/koto-action-bar.h"
#include "../../components/action-bar.h"
#include "../../components/track-item.h"
#include "../../db/cartographer.h"
#include "../../indexer/track-helpers.h"
#include "../../indexer/structs.h"
#include "../../koto-track-item.h"
#include "../../koto-utils.h"
#include "disc-view.h"
@ -194,7 +194,6 @@ void koto_disc_view_add_track(
KotoTrackItem * track_item = koto_track_item_new(track); // Create our new track item
if (!KOTO_IS_TRACK_ITEM(track_item)) { // Failed to create a track item for this track
g_warning("Failed to build track item for track with UUID %s", track_uuid);
return;
}
@ -212,9 +211,9 @@ void koto_disc_view_handle_selected_rows_changed(
return;
}
gchar * album_uuid = koto_album_get_album_uuid(self->album); // Get the UUID
gchar * album_uuid = koto_album_get_uuid(self->album); // Get the UUID
if (!koto_utils_is_string_valid(album_uuid)) { // Not set
if (!koto_utils_string_is_valid(album_uuid)) { // Not set
return;
}
@ -247,7 +246,7 @@ void koto_disc_view_remove_track(
return;
}
if (!koto_utils_is_string_valid(track_uuid)) { // If our UUID is not a valid
if (!koto_utils_string_is_valid(track_uuid)) { // If our UUID is not a valid
return;
}

View file

@ -17,9 +17,9 @@
#include <glib-2.0/glib.h>
#include <gtk-4.0/gtk/gtk.h>
#include "../../components/button.h"
#include "../../db/cartographer.h"
#include "../../indexer/structs.h"
#include "koto-button.h"
#include "config/config.h"
#include "../../koto-utils.h"
#include "music-local.h"
@ -154,7 +154,7 @@ void koto_page_music_local_go_to_artist_by_uuid(
NULL
);
if (!koto_utils_is_string_valid(artist_name)) { // Failed to get the artist name
if (!koto_utils_string_is_valid(artist_name)) { // Failed to get the artist name
return;
}

View file

@ -17,14 +17,14 @@
#include <glib-2.0/glib.h>
#include <gtk-4.0/gtk/gtk.h>
#include "../../components/koto-action-bar.h"
#include "../../components/koto-cover-art-button.h"
#include "../../components/action-bar.h"
#include "../../components/button.h"
#include "../../components/cover-art-button.h"
#include "../../components/track-table.h"
#include "../../db/cartographer.h"
#include "../../indexer/structs.h"
#include "../../playlist/current.h"
#include "../../playlist/playlist.h"
#include "../../koto-button.h"
#include "../../koto-window.h"
#include "../../playlist/create-modify-dialog.h"
#include "list.h"
@ -209,7 +209,7 @@ void koto_playlist_page_handle_cover_art_clicked(
return;
}
koto_current_playlist_set_playlist(current_playlist, self->playlist, TRUE); // Switch to this playlist and start playing immediately
koto_current_playlist_set_playlist(current_playlist, self->playlist, TRUE, FALSE); // Switch to this playlist and start playing immediately
}
void koto_playlist_page_handle_edit_button_clicked(
@ -245,13 +245,13 @@ void koto_playlist_page_handle_playlist_modified(
gchar * artwork = koto_playlist_get_artwork(playlist); // Get the artwork
if (koto_utils_is_string_valid(artwork)) { // Have valid artwork
if (koto_utils_string_is_valid(artwork)) { // Have valid artwork
koto_cover_art_button_set_art_path(self->playlist_image, artwork); // Update our Koto Cover Art Button
}
gchar * name = koto_playlist_get_name(playlist); // Get the name
if (koto_utils_is_string_valid(name)) { // Have valid name
if (koto_utils_string_is_valid(name)) { // Have valid name
gtk_label_set_label(GTK_LABEL(self->name_label), name); // Update the name label
}
}
@ -264,7 +264,7 @@ void koto_playlist_page_set_playlist_uuid(
return;
}
if (!koto_utils_is_string_valid(playlist_uuid)) { // Provided UUID string is not valid
if (!koto_utils_string_is_valid(playlist_uuid)) { // Provided UUID string is not valid
return;
}
@ -309,7 +309,7 @@ void koto_playlist_page_update_header(KotoPlaylistPage * self) {
gchar * artwork = koto_playlist_get_artwork(self->playlist);
if (koto_utils_is_string_valid(artwork)) { // Have artwork
if (koto_utils_string_is_valid(artwork)) { // Have artwork
koto_cover_art_button_set_art_path(self->playlist_image, artwork); // Update our artwork
}
}

View file

@ -19,7 +19,7 @@
#include <glib-2.0/glib-object.h>
#include <gtk-4.0/gtk/gtk.h>
#include "../../components/koto-action-bar.h"
#include "../../components/action-bar.h"
#include "../../playlist/playlist.h"
#include "../../koto-utils.h"

View file

@ -73,11 +73,14 @@ struct _KotoPlaybackEngine {
gboolean tick_duration_timer_running;
gboolean tick_track_timer_running;
gboolean tick_update_playlist_state;
gboolean via_config_continue_on_playlist; // Pulls from our Koto Config
gboolean via_config_maintain_shuffle; // Pulls from our Koto Config
guint playback_position;
gint requested_playback_position;
gdouble volume;
};
@ -214,7 +217,7 @@ static void koto_playback_engine_init(KotoPlaybackEngine * self) {
self->monitor = gst_bus_new(); // Get the bus for the playbin
self->duration_query = gst_query_new_duration(GST_FORMAT_TIME); // Create our re-usable query for querying the duration
self->position_query = gst_query_new_position(GST_FORMAT_DEFAULT); // Create our re-usable query for querying the position
self->position_query = gst_query_new_position(GST_FORMAT_TIME); // Create our re-usable query for querying the position
if (GST_IS_BUS(self->monitor)) {
gst_bus_add_watch(self->monitor, koto_playback_engine_monitor_changed, self);
@ -226,8 +229,10 @@ static void koto_playback_engine_init(KotoPlaybackEngine * self) {
self->is_playing_specific_track = FALSE;
self->is_repeat_enabled = FALSE;
self->is_shuffle_enabled = FALSE;
self->requested_playback_position = -1;
self->tick_duration_timer_running = FALSE;
self->tick_track_timer_running = FALSE;
self->tick_update_playlist_state = FALSE;
self->via_config_continue_on_playlist = FALSE;
self->via_config_maintain_shuffle = TRUE;
@ -247,6 +252,10 @@ void koto_playback_engine_apply_configuration_state(
guint prop_id,
KotoPlaybackEngine * self
) {
if (!KOTO_IS_PLAYBACK_ENGINE(self)) { // Not a playback engine
return;
}
(void) c;
(void) prop_id;
@ -274,6 +283,10 @@ void koto_playback_engine_apply_configuration_state(
}
void koto_playback_engine_backwards(KotoPlaybackEngine * self) {
if (!KOTO_IS_PLAYBACK_ENGINE(self)) { // Not a playback engine
return;
}
KotoPlaylist * playlist = koto_current_playlist_get_playlist(current_playlist); // Get the current playlist
if (!KOTO_IS_PLAYLIST(playlist)) { // If we do not have a playlist currently
@ -284,6 +297,7 @@ void koto_playback_engine_backwards(KotoPlaybackEngine * self) {
return;
}
koto_playback_engine_set_track_playback_position(self, 0); // Reset the current track position to 0
koto_playback_engine_set_track_by_uuid(self, koto_playlist_go_to_previous(playlist), FALSE);
}
@ -291,6 +305,7 @@ void koto_playback_engine_handle_current_playlist_changed(
KotoCurrentPlaylist * current_pl,
KotoPlaylist * playlist,
gboolean play_immediately,
gboolean play_current,
gpointer user_data
) {
(void) current_pl;
@ -305,11 +320,29 @@ void koto_playback_engine_handle_current_playlist_changed(
}
if (play_immediately) { // Should play immediately
koto_playback_engine_set_track_by_uuid(self, koto_playlist_go_to_next(playlist), FALSE); // Go to "next" which is the first track
gchar * play_uuid = NULL;
if (play_current) { // Play the current track
KotoTrack * track = koto_playlist_get_current_track(playlist); // Get the current track
if (KOTO_IS_TRACK(track)) {
play_uuid = koto_track_get_uuid(track);
}
}
if (!koto_utils_string_is_valid(play_uuid)) {
play_uuid = koto_playlist_go_to_next(playlist);
}
koto_playback_engine_set_track_by_uuid(self, play_uuid, FALSE); // Go to "next" which is the first track
}
}
void koto_playback_engine_forwards(KotoPlaybackEngine * self) {
if (!KOTO_IS_PLAYBACK_ENGINE(self)) { // Not a playback engine
return;
}
KotoPlaylist * playlist = koto_current_playlist_get_playlist(current_playlist); // Get the current playlist
if (!KOTO_IS_PLAYLIST(playlist)) { // If we do not have a playlist currently
@ -323,16 +356,21 @@ void koto_playback_engine_forwards(KotoPlaybackEngine * self) {
(self->via_config_continue_on_playlist && self->is_playing_specific_track) || // Playing a specific track and wanting to continue on the playlist
(!self->is_playing_specific_track) // Not playing a specific track
) {
koto_playback_engine_set_track_playback_position(self, 0); // Reset the current track position to 0
koto_playback_engine_set_track_by_uuid(self, koto_playlist_go_to_next(playlist), FALSE);
}
}
}
KotoTrack * koto_playback_engine_get_current_track(KotoPlaybackEngine * self) {
return self->current_track;
return KOTO_IS_PLAYBACK_ENGINE(self) ? self->current_track : NULL;
}
gint64 koto_playback_engine_get_duration(KotoPlaybackEngine * self) {
if (!KOTO_IS_PLAYBACK_ENGINE(self)) { // Not a playback engine
return 0;
}
guint64 track_duration = koto_track_get_duration(self->current_track); // Get the current track's duration
if (track_duration != 0) { // Have a duration stored
@ -350,6 +388,10 @@ gint64 koto_playback_engine_get_duration(KotoPlaybackEngine * self) {
}
gdouble koto_playback_engine_get_progress(KotoPlaybackEngine * self) {
if (!KOTO_IS_PLAYBACK_ENGINE(self)) { // Not a playback engine
return 0;
}
gdouble progress = 0.0;
gint64 gstprog = 0;
@ -360,7 +402,7 @@ gdouble koto_playback_engine_get_progress(KotoPlaybackEngine * self) {
return 0.0;
}
progress = gstprog / 100000;
progress = gstprog / GST_SECOND; // Devide by NS to get seconds
}
return progress;
@ -371,15 +413,15 @@ GstState koto_playback_engine_get_state(KotoPlaybackEngine * self) {
}
gboolean koto_playback_engine_get_track_repeat(KotoPlaybackEngine * self) {
return self->is_repeat_enabled;
return KOTO_IS_PLAYBACK_ENGINE(self) ? self->is_repeat_enabled : FALSE;
}
gboolean koto_playback_engine_get_track_shuffle(KotoPlaybackEngine * self) {
return self->is_shuffle_enabled;
return KOTO_IS_PLAYBACK_ENGINE(self) ? self->is_shuffle_enabled : FALSE;
}
gdouble koto_playback_engine_get_volume(KotoPlaybackEngine * self) {
return self->volume;
return KOTO_IS_PLAYBACK_ENGINE(self) ? self->volume : 0;
}
gboolean koto_playback_engine_monitor_changed(
@ -390,6 +432,10 @@ gboolean koto_playback_engine_monitor_changed(
(void) bus;
KotoPlaybackEngine * self = user_data;
if (!KOTO_IS_PLAYBACK_ENGINE(self)) {
return FALSE;
}
switch (GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_DURATION_CHANGED: { // Duration changed
koto_playback_engine_tick_duration(self);
@ -402,6 +448,15 @@ gboolean koto_playback_engine_monitor_changed(
GstState pending_state;
gst_message_parse_state_changed(msg, &old_state, &new_state, &pending_state);
gboolean possibly_buffered = (old_state == GST_STATE_READY) && (new_state == GST_STATE_PAUSED) && (pending_state == GST_STATE_PLAYING);
if (possibly_buffered && (self->requested_playback_position != -1)) { // Have a requested playback position
koto_playback_engine_set_position(self, self->requested_playback_position); // Set the position
self->requested_playback_position = -1;
koto_playback_engine_play(self); // Start playback
return TRUE;
}
if (new_state == GST_STATE_PLAYING) { // Now playing
koto_playback_engine_tick_duration(self);
g_signal_emit(self, playback_engine_signals[SIGNAL_IS_PLAYING], 0); // Emit our is playing state signal
@ -426,6 +481,10 @@ gboolean koto_playback_engine_monitor_changed(
}
void koto_playback_engine_play(KotoPlaybackEngine * self) {
if (!KOTO_IS_PLAYBACK_ENGINE(self)) {
return;
}
self->is_playing = TRUE;
gst_element_set_state(self->player, GST_STATE_PLAYING); // Set our state to play
@ -439,10 +498,19 @@ void koto_playback_engine_play(KotoPlaybackEngine * self) {
g_timeout_add(100, koto_playback_engine_tick_track, self); // Create a 100ms track tick
}
if (!self->tick_update_playlist_state) {
self->tick_update_playlist_state = TRUE;
g_timeout_add(60000, koto_playback_engine_tick_update_playlist_state, self); // Update the current playlist state every minute
}
koto_update_mpris_playback_state(GST_STATE_PLAYING);
}
void koto_playback_engine_pause(KotoPlaybackEngine * self) {
if (!KOTO_IS_PLAYBACK_ENGINE(self)) {
return;
}
self->is_playing = FALSE;
gst_element_change_state(self->player, GST_STATE_CHANGE_PLAYING_TO_PAUSED);
koto_update_mpris_playback_state(GST_STATE_PAUSED);
@ -452,6 +520,10 @@ void koto_playback_engine_set_position(
KotoPlaybackEngine * self,
int position
) {
if (!KOTO_IS_PLAYBACK_ENGINE(self)) {
return;
}
gst_element_seek(
self->playbin,
1.0,
@ -464,6 +536,21 @@ void koto_playback_engine_set_position(
);
}
void koto_playback_engine_set_track_playback_position(
KotoPlaybackEngine * self,
guint64 pos
) {
if (!KOTO_IS_PLAYBACK_ENGINE(self)) {
return;
}
if (!KOTO_IS_TRACK(self->current_track) || self->is_repeat_enabled) { // If we do not have a track or repeating
return;
}
koto_track_set_playback_position(self->current_track, pos); // Set the playback position of the track
}
void koto_playback_engine_set_track_repeat(
KotoPlaybackEngine * self,
gboolean enable_repeat
@ -476,6 +563,10 @@ void koto_playback_engine_set_track_shuffle(
KotoPlaybackEngine * self,
gboolean enable_shuffle
) {
if (!KOTO_IS_PLAYBACK_ENGINE(self)) {
return;
}
KotoPlaylist * playlist = koto_current_playlist_get_playlist(current_playlist);
if (!KOTO_IS_PLAYLIST(playlist)) { // Don't have a playlist currently
@ -496,7 +587,7 @@ void koto_playback_engine_set_track_by_uuid(
return;
}
if (!koto_utils_is_string_valid(track_uuid)) { // If this is not a valid track uuid string
if (!koto_utils_string_is_valid(track_uuid)) { // If this is not a valid track uuid string
return;
}
@ -519,10 +610,8 @@ void koto_playback_engine_set_track_by_uuid(
g_object_set(self->playbin, "uri", gst_filename, NULL);
g_free(gst_filename); // Free the filename
self->requested_playback_position = koto_track_get_playback_position(self->current_track); // Set our requested playback position
koto_playback_engine_play(self); // Play the new track
// TODO: Add prior position state setting here, like picking up at a specific part of an audiobook or podcast
koto_playback_engine_set_position(self, 0);
koto_playback_engine_set_volume(self, self->volume); // Re-enforce our volume on the updated playbin
GVariant * metadata = koto_track_get_metadata_vardict(track); // Get the GVariantBuilder variable dict for the metadata
@ -540,14 +629,14 @@ void koto_playback_engine_set_track_by_uuid(
GVariant * artist_name_var = g_variant_dict_lookup_value(metadata_dict, "playbackengine:artist", NULL); // Get the GVariant for the name of the artist
gchar * artist_name = g_strdup(g_variant_get_string(artist_name_var, NULL)); // Get the string for the artist name
gchar * artist_album_combo = koto_utils_is_string_valid(album_name) ? g_strjoin(" - ", artist_name, album_name, NULL) : artist_name; // Join artist and album name separated by " - "
gchar * artist_album_combo = koto_utils_string_is_valid(album_name) ? g_strjoin(" - ", artist_name, album_name, NULL) : artist_name; // Join artist and album name separated by " - "
gchar * icon_name = "audio-x-generic-symbolic";
if (g_variant_dict_contains(metadata_dict, "mpris:artUrl")) { // If we have artwork specified
GVariant * art_url_var = g_variant_dict_lookup_value(metadata_dict, "mpris:artUrl", NULL); // Get the GVariant for the art URL
const gchar * art_uri = g_variant_get_string(art_url_var, NULL); // Get the string for the artwork
icon_name = koto_utils_replace_string_all(g_strdup(art_uri), "file://", "");
icon_name = koto_utils_string_replace_all(g_strdup(art_uri), "file://", "");
}
// Super important note: We are not using libnotify directly because the synchronous nature of notify_notification_send seems to result in dbus timeouts
@ -577,11 +666,19 @@ void koto_playback_engine_set_volume(
KotoPlaybackEngine * self,
gdouble volume
) {
if (!KOTO_IS_PLAYBACK_ENGINE(self)) {
return;
}
self->volume = volume;
g_object_set(self->playbin, "volume", self->volume, NULL);
}
void koto_playback_engine_stop(KotoPlaybackEngine * self) {
if (!KOTO_IS_PLAYBACK_ENGINE(self)) {
return;
}
gst_element_set_state(self->player, GST_STATE_NULL);
GstPad * pad = gst_element_get_static_pad(self->player, "sink"); // Get the static pad of the audio element
@ -594,16 +691,26 @@ void koto_playback_engine_stop(KotoPlaybackEngine * self) {
}
void koto_playback_engine_toggle(KotoPlaybackEngine * self) {
if (!KOTO_IS_PLAYBACK_ENGINE(self)) {
return;
}
if (self->is_playing) { // Currently playing
koto_playback_engine_pause(self); // Pause
} else {
koto_playback_engine_play(self); // Play
}
koto_current_playlist_save_playlist_state(current_playlist); // Save the playlist state during pause and play in case we are doing that remotely
}
gboolean koto_playback_engine_tick_duration(gpointer user_data) {
KotoPlaybackEngine * self = user_data;
if (!KOTO_IS_PLAYBACK_ENGINE(self)) {
return FALSE;
}
if (self->is_playing) { // Is playing
g_signal_emit(self, playback_engine_signals[SIGNAL_TICK_DURATION], 0); // Emit our 1s track tick
} else { // Not playing so exiting timer
@ -616,23 +723,67 @@ gboolean koto_playback_engine_tick_duration(gpointer user_data) {
gboolean koto_playback_engine_tick_track(gpointer user_data) {
KotoPlaybackEngine * self = user_data;
if (!KOTO_IS_PLAYBACK_ENGINE(self)) {
return FALSE;
}
if (self->is_playing) { // Is playing
KotoPlaylist * playlist = koto_current_playlist_get_playlist(current_playlist);
if (KOTO_IS_PLAYLIST(playlist)) {
koto_playlist_emit_modified(playlist); // Emit modified on the playlist to update UI in realtime (ish, okay? every 100ms is enough geez)
}
g_signal_emit(self, playback_engine_signals[SIGNAL_TICK_TRACK], 0); // Emit our 100ms track tick
} else {
self->tick_track_timer_running = FALSE;
}
koto_playback_engine_update_track_position(self); // Update track position if needed
return self->is_playing;
}
gboolean koto_playback_engine_tick_update_playlist_state(gpointer user_data) {
KotoPlaybackEngine * self = user_data;
if (!KOTO_IS_PLAYBACK_ENGINE(self)) {
return FALSE;
}
if (self->is_playing) { // Is playing
koto_current_playlist_save_playlist_state(current_playlist); // Save the state of the current playlist
} else {
self->tick_update_playlist_state = FALSE;
}
return self->is_playing;
}
void koto_playback_engine_toggle_track_repeat(KotoPlaybackEngine * self) {
if (!KOTO_IS_PLAYBACK_ENGINE(self)) {
return;
}
koto_playback_engine_set_track_repeat(self, !self->is_repeat_enabled);
}
void koto_playback_engine_toggle_track_shuffle(KotoPlaybackEngine * self) {
if (!KOTO_IS_PLAYBACK_ENGINE(self)) {
return;
}
koto_playback_engine_set_track_shuffle(self, !self->is_shuffle_enabled); // Invert the currently shuffling vale
}
void koto_playback_engine_update_track_position(KotoPlaybackEngine * self) {
if (!KOTO_IS_PLAYBACK_ENGINE(self)) {
return;
}
koto_playback_engine_set_track_playback_position(self, (guint64) koto_playback_engine_get_progress(self)); // Set to current progress
}
KotoPlaybackEngine * koto_playback_engine_new() {
return g_object_new(KOTO_TYPE_PLAYBACK_ENGINE, NULL);
}

View file

@ -56,6 +56,7 @@ void koto_playback_engine_handle_current_playlist_changed(
KotoCurrentPlaylist * current_pl,
KotoPlaylist * playlist,
gboolean play_immediately,
gboolean play_current,
gpointer user_data
);
@ -94,6 +95,11 @@ void koto_playback_engine_set_position(
int position
);
void koto_playback_engine_set_track_playback_position(
KotoPlaybackEngine * self,
guint64 pos
);
void koto_playback_engine_set_track_repeat(
KotoPlaybackEngine * self,
gboolean enable_repeat
@ -123,6 +129,10 @@ void koto_playback_engine_toggle_track_shuffle(KotoPlaybackEngine * self);
void koto_playback_engine_update_duration(KotoPlaybackEngine * self);
void koto_playback_engine_update_track_position(KotoPlaybackEngine * self);
gboolean koto_playback_engine_tick_duration(gpointer user_data);
gboolean koto_playback_engine_tick_track(gpointer user_data);
gboolean koto_playback_engine_tick_update_playlist_state(gpointer user_data);

View file

@ -41,7 +41,7 @@ gboolean koto_playback_engine_gst_caps_iter(
g_hash_table_add(supported_mimes_hash, g_strdup(caps_name));
supported_mimes = g_list_prepend(supported_mimes, g_strdup(caps_name));
supported_mimes = g_list_prepend(supported_mimes, g_strdup(koto_utils_replace_string_all(caps_name, "x-", "")));
supported_mimes = g_list_prepend(supported_mimes, g_strdup(koto_utils_string_replace_all(caps_name, "x-", "")));
return TRUE;
}

View file

@ -150,11 +150,10 @@ void koto_add_remove_track_popover_handle_checkbutton_toggle(
continue; // Skip this
}
gchar * track_uuid = koto_track_get_uuid(track); // Get the track
if (should_add) { // Should be adding
koto_playlist_add_track_by_uuid(playlist, track_uuid, FALSE, TRUE); // Add the track to the playlist
koto_playlist_add_track(playlist, track, FALSE, TRUE); // Add the track to the playlist
} else { // Should be removing
gchar * track_uuid = koto_track_get_uuid(track); // Get the track
koto_playlist_remove_track_by_uuid(playlist, track_uuid); // Remove the track from the playlist
}
}

View file

@ -205,7 +205,7 @@ void koto_create_modify_playlist_dialog_handle_create_click(
}
KotoPlaylist * playlist = NULL;
gboolean modify_existing_playlist = koto_utils_is_string_valid(self->playlist_uuid);
gboolean modify_existing_playlist = koto_utils_string_is_valid(self->playlist_uuid);
if (modify_existing_playlist) { // Modifying an existing playlist
playlist = koto_cartographer_get_playlist_by_uuid(koto_maps, self->playlist_uuid);
@ -313,13 +313,14 @@ void koto_create_modify_playlist_dialog_reset(KotoCreateModifyPlaylistDialog * s
gtk_entry_set_placeholder_text(GTK_ENTRY(self->name_entry), "Name of playlist"); // Reset placeholder
gtk_image_set_from_icon_name(GTK_IMAGE(self->playlist_image), "insert-image-symbolic"); // Reset the image
gtk_button_set_label(GTK_BUTTON(self->create_button), "Create");
self->playlist_uuid = NULL; // Reset playlist UUID
}
void koto_create_modify_playlist_dialog_set_playlist_uuid(
KotoCreateModifyPlaylistDialog * self,
gchar * playlist_uuid
) {
if (!koto_utils_is_string_valid(playlist_uuid)) { // Not a valid playlist UUID string
if (!koto_utils_string_is_valid(playlist_uuid)) { // Not a valid playlist UUID string
return;
}
@ -335,7 +336,7 @@ void koto_create_modify_playlist_dialog_set_playlist_uuid(
gchar * art = koto_playlist_get_artwork(playlist);
if (!koto_utils_is_string_valid(art)) { // If art is not defined
if (!koto_utils_string_is_valid(art)) { // If art is not defined
gtk_image_set_from_icon_name(GTK_IMAGE(self->playlist_image), "insert-image-symbolic"); // Reset the image
} else {
gtk_image_set_from_file(GTK_IMAGE(self->playlist_image), art);

View file

@ -58,8 +58,9 @@ static void koto_current_playlist_class_init(KotoCurrentPlaylistClass * c) {
NULL,
NULL,
G_TYPE_NONE,
2,
3,
KOTO_TYPE_PLAYLIST,
G_TYPE_BOOLEAN,
G_TYPE_BOOLEAN
);
}
@ -71,13 +72,26 @@ static void koto_current_playlist_init(KotoCurrentPlaylist * self) {
}
KotoPlaylist * koto_current_playlist_get_playlist(KotoCurrentPlaylist * self) {
return self->current_playlist;
return KOTO_IS_CURRENT_PLAYLIST(self) ? self->current_playlist : NULL;
}
void koto_current_playlist_save_playlist_state(KotoCurrentPlaylist * self) {
if (!KOTO_IS_CURRENT_PLAYLIST(self)) { // Not a CurrentPlaylist
return;
}
if (!KOTO_IS_PLAYLIST(self->current_playlist)) { // No playlist
return;
}
koto_playlist_save_current_playback_state(self->current_playlist); // Save the current playlist state
}
void koto_current_playlist_set_playlist(
KotoCurrentPlaylist * self,
KotoPlaylist * playlist,
gboolean play_immediately
gboolean play_immediately,
gboolean play_current
) {
if (!KOTO_IS_CURRENT_PLAYLIST(self)) {
return;
@ -88,6 +102,8 @@ void koto_current_playlist_set_playlist(
}
if (KOTO_IS_PLAYLIST(self->current_playlist)) {
koto_current_playlist_save_playlist_state(self); // Save the current playlist state if needed
gboolean * is_temp = FALSE;
g_object_get(self->current_playlist, "ephemeral", &is_temp, NULL); // Get the current ephemeral value
@ -101,15 +117,14 @@ void koto_current_playlist_set_playlist(
}
self->current_playlist = playlist;
// TODO: Saved state
koto_playlist_set_position(self->current_playlist, -1); // Reset our position, use -1 since "next" song is then 0
g_object_ref(playlist); // Increment the reference
g_signal_emit(
self,
signals[SIGNAL_PLAYLIST_CHANGED],
0,
playlist,
play_immediately
play_immediately,
play_current
);
}

View file

@ -43,10 +43,13 @@ KotoCurrentPlaylist * koto_current_playlist_new();
KotoPlaylist * koto_current_playlist_get_playlist(KotoCurrentPlaylist * self);
void koto_current_playlist_save_playlist_state(KotoCurrentPlaylist * self);
void koto_current_playlist_set_playlist(
KotoCurrentPlaylist * self,
KotoPlaylist * playlist,
gboolean play_immediately
gboolean play_immediately,
gboolean play_current
);
G_END_DECLS

View file

@ -30,6 +30,7 @@ enum {
PROP_0,
PROP_UUID,
PROP_NAME,
PROP_ALBUM_UUID,
PROP_ART_PATH,
PROP_EPHEMERAL,
PROP_IS_SHUFFLE_ENABLED,
@ -49,10 +50,11 @@ struct _KotoPlaylist {
gchar * uuid;
gchar * name;
gchar * art_path;
gchar * album_uuid;
gint current_position;
gchar * current_uuid;
KotoPreferredModelType model;
KotoPreferredPlaylistSortType model;
gboolean ephemeral;
gboolean is_shuffle_enabled;
@ -127,6 +129,14 @@ static void koto_playlist_class_init(KotoPlaylistClass * c) {
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
);
props[PROP_ALBUM_UUID] = g_param_spec_string(
"album-uuid",
"UUID of an Album associated with this Playlist",
"UUID of an Album associated with this Playlist. Useful for Audiobooks.",
NULL,
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
);
props[PROP_ART_PATH] = g_param_spec_string(
"art-path",
"Path to any associated artwork of the Playlist",
@ -219,6 +229,9 @@ static void koto_playlist_get_property(
case PROP_NAME:
g_value_set_string(val, self->name);
break;
case PROP_ALBUM_UUID:
g_value_set_string(val, self->album_uuid);
break;
case PROP_ART_PATH:
g_value_set_string(val, self->art_path);
break;
@ -249,6 +262,9 @@ static void koto_playlist_set_property(
case PROP_NAME:
koto_playlist_set_name(self, g_value_get_string(val));
break;
case PROP_ALBUM_UUID:
koto_playlist_set_album_uuid(self, g_value_get_string(val));
break;
case PROP_ART_PATH:
koto_playlist_set_artwork(self, g_value_get_string(val));
break;
@ -267,7 +283,7 @@ static void koto_playlist_set_property(
static void koto_playlist_init(KotoPlaylist * self) {
self->current_position = -1; // Default to -1 so first time incrementing puts it at 0
self->current_uuid = NULL;
self->model = KOTO_PREFERRED_MODEL_TYPE_DEFAULT; // Default to default model
self->model = KOTO_PREFERRED_PLAYLIST_SORT_TYPE_DEFAULT; // Default to default model
self->is_shuffle_enabled = FALSE;
self->ephemeral = FALSE;
self->finalized = FALSE;
@ -282,7 +298,17 @@ void koto_playlist_add_to_played_tracks(
KotoPlaylist * self,
gchar * uuid
) {
if (g_queue_index(self->played_tracks, uuid) != -1) { // Already added
if (!KOTO_IS_PLAYLIST(self)) {
return;
}
KotoTrack * track = koto_cartographer_get_track_by_uuid(koto_maps, uuid);
if (!KOTO_IS_TRACK(track)) {
return;
}
if (koto_playlist_get_position_of_track(self, track) != -1) { // Already added
return;
}
@ -295,32 +321,22 @@ void koto_playlist_add_track(
gboolean current,
gboolean commit_to_table
) {
koto_playlist_add_track_by_uuid(self, koto_track_get_uuid(track), current, commit_to_table);
}
void koto_playlist_add_track_by_uuid(
KotoPlaylist * self,
gchar * uuid,
gboolean current,
gboolean commit_to_table
) {
KotoTrack * track = koto_cartographer_get_track_by_uuid(koto_maps, uuid); // Get the track
if (!KOTO_IS_PLAYLIST(self)) {
return;
}
if (!KOTO_IS_TRACK(track)) {
return;
}
GList * found_tracks_uuids = g_queue_find_custom(self->tracks, uuid, koto_playlist_compare_track_uuids);
gchar * track_uuid = koto_track_get_uuid(track);
if (found_tracks_uuids != NULL) { // Is somewhere in the tracks already
g_list_free(found_tracks_uuids);
if (koto_playlist_get_position_of_track(self, track) != -1) { // Found already
return;
}
g_list_free(found_tracks_uuids);
g_queue_push_tail(self->tracks, uuid); // Prepend the UUID to the tracks
g_queue_push_tail(self->sorted_tracks, uuid); // Also add to our sorted tracks
g_queue_push_tail(self->tracks, track_uuid); // Prepend the UUID to the tracks
g_queue_push_tail(self->sorted_tracks, track_uuid); // Also add to our sorted tracks
g_list_store_append(self->store, track); // Add to the store
if (self->finalized) { // Is already finalized
@ -328,24 +344,25 @@ void koto_playlist_add_track_by_uuid(
}
if (commit_to_table) {
koto_track_save_to_playlist(track, self->uuid, (g_strcmp0(self->current_uuid, uuid) == 0) ? 1 : 0); // Call to save the playlist to the track
koto_track_save_to_playlist(track, self->uuid); // Call to save the playlist to the track
}
if (current && (g_queue_get_length(self->tracks) > 1)) { // Is current and NOT the first item
self->current_uuid = uuid; // Mark this as current UUID
self->current_uuid = track_uuid; // Mark this as current UUID
koto_playlist_apply_model(self, self->model); // Re-apply our model to enforce mass sort
}
g_signal_emit(
self,
playlist_signals[SIGNAL_TRACK_ADDED],
0,
uuid
track_uuid
);
}
void koto_playlist_apply_model(
KotoPlaylist * self,
KotoPreferredModelType preferred_model
KotoPreferredPlaylistSortType preferred_model
) {
GList * sort_user_data = NULL;
@ -359,68 +376,97 @@ void koto_playlist_apply_model(
}
void koto_playlist_commit(KotoPlaylist * self) {
if (self->ephemeral) { // Temporary playlist
if (!KOTO_IS_PLAYLIST(self)) {
return;
}
gboolean album_acceptable_type = FALSE;
if (koto_utils_string_is_valid(self->album_uuid)) {
KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, self->album_uuid); // Get the album
if (KOTO_IS_ALBUM(album)) { // Got the album
KotoLibraryType album_type = koto_album_get_lib_type(album);
album_acceptable_type = ((album_type == KOTO_LIBRARY_TYPE_AUDIOBOOK) || (album_type == KOTO_LIBRARY_TYPE_PODCAST));
}
}
if (self->ephemeral && !album_acceptable_type) { // Is a temporary playlist and NOT an acceptable type
return;
}
guint64 track_playback_pos = 0;
if (koto_utils_string_is_valid(self->current_uuid)) { // Have a track UUID
KotoTrack * track = koto_cartographer_get_track_by_uuid(koto_maps, self->current_uuid); // Get the track
if (KOTO_IS_TRACK(track)) { // Is a track
track_playback_pos = koto_track_get_playback_position(track); // Get the track playback position and set it to our track_playback_pos
}
}
gchar * commit_op = g_strdup_printf(
"INSERT INTO playlist_meta(id, name, art_path, preferred_model)"
"VALUES('%s', quote(\"%s\"), quote(\"%s\"), 0)"
"ON CONFLICT(id) DO UPDATE SET name=excluded.name, art_path=excluded.art_path;",
"INSERT INTO playlist_meta(id, name, art_path, preferred_model, album_id, track_id, playback_position_of_track)"
"VALUES('%s', quote(\"%s\"), quote(\"%s\"), %li, '%s', '%s', '%li')"
"ON CONFLICT(id) DO UPDATE SET name=excluded.name, art_path=excluded.art_path, preferred_model=excluded.preferred_model, album_id=excluded.album_id, track_id=excluded.track_id;",
self->uuid,
self->name,
self->art_path
koto_utils_string_get_valid(self->name),
koto_utils_string_get_valid(self->art_path),
(guint64) self->model,
koto_utils_string_get_valid(self->album_uuid),
koto_utils_string_get_valid(self->current_uuid),
track_playback_pos
);
new_transaction(commit_op, "Failed to save playlist", FALSE);
}
void koto_playlist_commit_tracks(
gpointer data,
gpointer user_data
) {
KotoTrack * track = koto_cartographer_get_track_by_uuid(koto_maps, data); // Get the track
if (track == NULL) { // Not a track
KotoPlaylist * self = user_data;
gchar * playlist_uuid = self->uuid; // Get the playlist UUID
gchar * current_track = g_queue_peek_nth(self->tracks, self->current_position); // Get the UUID of the current track
//koto_track_save_to_playlist(track, playlist_uuid, (data == current_track) ? 1 : 0); // Call to save the playlist to the track
g_free(playlist_uuid);
g_free(current_track);
void koto_playlist_emit_modified(KotoPlaylist * self) {
if (!KOTO_IS_PLAYLIST(self)) {
return;
}
}
gint koto_playlist_compare_track_uuids(
gconstpointer a,
gconstpointer b
) {
return g_strcmp0(a, b);
g_signal_emit(
self,
playlist_signals[SIGNAL_MODIFIED],
0
);
}
gchar * koto_playlist_get_artwork(KotoPlaylist * self) {
return (self->art_path == NULL) ? NULL : g_strdup(self->art_path); // Return a duplicate of our art path
return (KOTO_IS_PLAYLIST(self) && koto_utils_string_is_valid(self->art_path)) ? g_strdup(self->art_path) : NULL; // Return a duplicate of our art path
}
KotoPreferredModelType koto_playlist_get_current_model(KotoPlaylist * self) {
return self->model;
KotoPreferredPlaylistSortType koto_playlist_get_current_model(KotoPlaylist * self) {
return KOTO_IS_PLAYLIST(self) ? self->model : KOTO_PREFERRED_PLAYLIST_SORT_TYPE_DEFAULT;
}
guint koto_playlist_get_current_position(KotoPlaylist * self) {
return self->current_position;
gint koto_playlist_get_current_position(KotoPlaylist * self) {
return KOTO_IS_PLAYLIST(self) ? self->current_position : -1;
}
KotoTrack * koto_playlist_get_current_track(KotoPlaylist * self) {
if (!KOTO_IS_PLAYLIST(self)) { // Not a playlist
return NULL;
}
return koto_cartographer_get_track_by_uuid(koto_maps, self->current_uuid); // Get the current track
}
gboolean koto_playlist_get_is_finalized(KotoPlaylist * self) {
return self->finalized;
return KOTO_IS_PLAYLIST(self) ? self->finalized : FALSE;
}
gboolean koto_playlist_get_is_hidden(KotoPlaylist * self) {
return (self->ephemeral && koto_utils_string_is_valid(self->album_uuid)); // If the playlist is ephemeral and associated with an album, it should be hidden from nav
}
guint koto_playlist_get_length(KotoPlaylist * self) {
return g_queue_get_length(self->tracks); // Get the length of the tracks
return KOTO_IS_PLAYLIST(self) ? g_queue_get_length(self->tracks) : 0; // Get the length of the tracks
}
gchar * koto_playlist_get_name(KotoPlaylist * self) {
return (self->name == NULL) ? NULL : g_strdup(self->name);
return (KOTO_IS_PLAYLIST(self) && koto_utils_string_is_valid(self->name)) ? g_strdup(self->name) : NULL;
}
gint koto_playlist_get_position_of_track(
@ -501,11 +547,15 @@ gchar * koto_playlist_go_to_next(KotoPlaylist * self) {
if (self->is_shuffle_enabled) { // Shuffling enabled
gchar * random_track_uuid = koto_playlist_get_random_track(self); // Get a random track
self->current_uuid = random_track_uuid; // Update our current UUID
koto_playlist_add_to_played_tracks(self, random_track_uuid);
koto_playlist_emit_modified(self);
return random_track_uuid;
}
if (!koto_utils_is_string_valid(self->current_uuid)) { // No valid UUID yet
if (!koto_utils_string_is_valid(self->current_uuid)) { // No valid UUID yet
self->current_position++;
} else { // Have a UUID currently
KotoTrack * track = koto_cartographer_get_track_by_uuid(koto_maps, self->current_uuid);
@ -525,6 +575,7 @@ gchar * koto_playlist_go_to_next(KotoPlaylist * self) {
self->current_uuid = g_queue_peek_nth(self->sorted_tracks, self->current_position);
koto_playlist_add_to_played_tracks(self, self->current_uuid);
koto_playlist_emit_modified(self);
return self->current_uuid;
}
@ -534,7 +585,7 @@ gchar * koto_playlist_go_to_previous(KotoPlaylist * self) {
return koto_playlist_get_random_track(self); // Get a random track
}
if (!koto_utils_is_string_valid(self->current_uuid)) { // No valid UUID
if (!koto_utils_string_is_valid(self->current_uuid)) { // No valid UUID
return NULL;
}
@ -553,11 +604,13 @@ gchar * koto_playlist_go_to_previous(KotoPlaylist * self) {
self->current_position = pos_of_song - 1; // Decrement our position based on position of song
self->current_uuid = g_queue_peek_nth(self->sorted_tracks, self->current_position);
koto_playlist_emit_modified(self);
return self->current_uuid;
}
void koto_playlist_mark_as_finalized(KotoPlaylist * self) {
if (self->finalized) { // Already finalized
if (!KOTO_IS_PLAYLIST(self)) { // Not a playlist
return;
}
@ -592,11 +645,11 @@ gint koto_playlist_model_sort_by_track(
GList * ptr_list = data_list;
KotoPlaylist * self = g_list_nth_data(ptr_list, 0); // First item in the GPtrArray is a pointer to our playlist
KotoPreferredModelType model = GPOINTER_TO_UINT(g_list_nth_data(ptr_list, 1)); // Second item in the GPtrArray is a pointer to our KotoPreferredModelType
KotoPreferredPlaylistSortType model = GPOINTER_TO_UINT(g_list_nth_data(ptr_list, 1)); // Second item in the GPtrArray is a pointer to our KotoPreferredPlaylistSortType
if (
(model == KOTO_PREFERRED_MODEL_TYPE_DEFAULT) || // Newest first model
(model == KOTO_PREFERRED_MODEL_TYPE_OLDEST_FIRST) // Oldest first
(model == KOTO_PREFERRED_PLAYLIST_SORT_TYPE_DEFAULT) || // Newest first model
(model == KOTO_PREFERRED_PLAYLIST_SORT_TYPE_OLDEST_FIRST) // Oldest first
) {
gint first_track_pos = g_queue_index(self->tracks, koto_track_get_uuid(first_track));
gint second_track_pos = g_queue_index(self->tracks, koto_track_get_uuid(second_track));
@ -609,14 +662,14 @@ gint koto_playlist_model_sort_by_track(
return -1;
}
if (model == KOTO_PREFERRED_MODEL_TYPE_DEFAULT) { // Newest first
if (model == KOTO_PREFERRED_PLAYLIST_SORT_TYPE_DEFAULT) { // Newest first
return (first_track_pos < second_track_pos) ? 1 : -1; // Display first at end, not beginning
} else {
return (first_track_pos < second_track_pos) ? -1 : 1; // Display at beginning, not end
}
}
if (model == KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ALBUM) { // Sort by album name
if (model == KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_ALBUM) { // Sort by album name
gchar * first_album_uuid = NULL;
gchar * second_album_uuid = NULL;
@ -653,7 +706,7 @@ gint koto_playlist_model_sort_by_track(
return g_utf8_collate(koto_album_get_name(first_album), koto_album_get_name(second_album));
}
if (model == KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ARTIST) { // Sort by artist name
if (model == KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_ARTIST) { // Sort by artist name
gchar * first_artist_uuid = NULL;
gchar * second_artist_uuid = NULL;
@ -682,31 +735,27 @@ gint koto_playlist_model_sort_by_track(
}
return g_utf8_collate(koto_artist_get_name(first_artist), koto_artist_get_name(second_artist));
}
} else if (model == KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_TRACK_NAME) { // Track name
return g_utf8_collate(koto_track_get_name(first_track), koto_track_get_name(second_track));
} else if (model == KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_TRACK_POS) { // Sort by track position
if (model == KOTO_PREFERRED_MODEL_TYPE_SORT_BY_TRACK_NAME) { // Track name
gchar * first_track_name = NULL;
gchar * second_track_name = NULL;
guint first_track_disc = koto_track_get_disc_number(first_track);
guint second_track_disc = koto_track_get_disc_number(second_track);
g_object_get(
first_track,
"parsed-name",
&first_track_name,
NULL
);
if (first_track_disc < second_track_disc) { // First is in an earlier CD / Disc / Part
return -1;
} else if (first_track_disc > second_track_disc) { // First is in later CD / Disc / Part
return 1;
} // Continue on to same CD
g_object_get(
second_track,
"parsed-name",
&second_track_name,
NULL
);
guint64 first_track_pos = koto_track_get_position(first_track);
guint64 second_track_pos = koto_track_get_position(second_track);
gint ret = g_utf8_collate(first_track_name, second_track_name);
g_free(first_track_name);
g_free(second_track_name);
return ret;
if (first_track_pos < second_track_pos) {
return -1;
} else if (first_track_pos > second_track_pos) {
return 1;
}
}
return 0;
@ -761,10 +810,73 @@ void koto_playlist_remove_track_by_uuid(
);
}
void koto_playlist_save_current_playback_state(KotoPlaylist * self) {
if (!KOTO_IS_PLAYLIST(self)) {
return;
}
if (!koto_utils_string_is_valid(self->album_uuid)) { // No album associated with this playlist
return;
}
KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, self->album_uuid); // Get the album
if (!KOTO_IS_ALBUM(album)) { // Failed to get the album
return;
}
KotoLibraryType album_type = koto_album_get_lib_type(album);
if ((album_type == KOTO_LIBRARY_TYPE_MUSIC) || (album_type == KOTO_LIBRARY_TYPE_UNKNOWN)) { // Not an Audiobook or Podcast
return;
}
KotoTrack * track = koto_cartographer_get_track_by_uuid(koto_maps, self->current_uuid); // Get the track
if (!KOTO_IS_TRACK(track) || !koto_utils_string_is_valid(self->current_uuid)) { // If we don't have a track we are currently playing
gchar * commit_op = g_strdup_printf("UPDATE playlist_meta SET track_id='', playback_position_of_track=0 WHERE id='%s';", self->uuid);
new_transaction(commit_op, "Failed to update our playlist meta", FALSE);
return;
}
gchar * commit_op = g_strdup_printf(
"UPDATE playlist_meta SET track_id='%s', playback_position_of_track=%li WHERE id='%s';",
koto_utils_string_get_valid(self->current_uuid),
koto_track_get_playback_position(track),
self->uuid
);
new_transaction(commit_op, "Failed to update our playlist state", FALSE);
koto_playlist_emit_modified(self);
}
void koto_playlist_set_album_uuid(
KotoPlaylist * self,
const gchar * album_uuid
) {
if (!KOTO_IS_PLAYLIST(self)) {
return;
}
if (!koto_utils_string_is_valid(album_uuid)) {
return;
}
self->album_uuid = g_strdup(album_uuid); // Update the album UUID
if (self->finalized) { // Has already been loaded
koto_playlist_emit_modified(self);
}
}
void koto_playlist_set_artwork(
KotoPlaylist * self,
const gchar * path
) {
if (!KOTO_IS_PLAYLIST(self)) {
return;
}
if (path == NULL) {
return;
}
@ -792,11 +904,7 @@ void koto_playlist_set_artwork(
self->art_path = g_strdup(path); // Update our art path to a duplicate of provided path
if (self->finalized) { // Has already been loaded
g_signal_emit(
self,
playlist_signals[SIGNAL_MODIFIED],
0
);
koto_playlist_emit_modified(self);
}
free_cookie:
@ -818,11 +926,7 @@ void koto_playlist_set_name(
self->name = g_strdup(name);
if (self->finalized) { // Has already been loaded
g_signal_emit(
self,
playlist_signals[SIGNAL_MODIFIED],
0
);
koto_playlist_emit_modified(self);
}
}
@ -830,19 +934,39 @@ void koto_playlist_set_position(
KotoPlaylist * self,
gint position
) {
if (!KOTO_IS_PLAYLIST(self)) { // Not a playlist
return;
}
self->current_position = position;
if (self->finalized) { // Has already been loaded
koto_playlist_emit_modified(self);
}
}
void koto_playlist_set_track_as_current(
KotoPlaylist * self,
gchar * track_uuid
) {
gint position_of_track = g_queue_index(self->sorted_tracks, track_uuid); // Get the position of the UUID in our tracks
if (position_of_track != -1) { // In tracks
self->current_uuid = track_uuid;
self->current_position = position_of_track;
if (!KOTO_IS_PLAYLIST(self)) { // Not a playlist
return;
}
KotoTrack * track = koto_cartographer_get_track_by_uuid(koto_maps, track_uuid); // Get the track
if (!KOTO_IS_TRACK(track)) { // Not a track
return;
}
gint position_of_track = koto_playlist_get_position_of_track(self, track);
if (position_of_track == -1) {
return;
}
self->current_uuid = track_uuid;
self->current_position = position_of_track; // Set accurate position
}
void koto_playlist_set_uuid(

View file

@ -23,12 +23,13 @@
G_BEGIN_DECLS
typedef enum {
KOTO_PREFERRED_MODEL_TYPE_DEFAULT, // Considered to be newest first
KOTO_PREFERRED_MODEL_TYPE_OLDEST_FIRST,
KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ALBUM,
KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ARTIST,
KOTO_PREFERRED_MODEL_TYPE_SORT_BY_TRACK_NAME
} KotoPreferredModelType;
KOTO_PREFERRED_PLAYLIST_SORT_TYPE_DEFAULT, // Considered to be newest first
KOTO_PREFERRED_PLAYLIST_SORT_TYPE_OLDEST_FIRST,
KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_ALBUM,
KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_ARTIST,
KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_TRACK_NAME,
KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_TRACK_POS
} KotoPreferredPlaylistSortType;
/**
* Type Definition
@ -64,40 +65,29 @@ void koto_playlist_add_track(
gboolean commit_to_table
);
void koto_playlist_add_track_by_uuid(
KotoPlaylist * self,
gchar * uuid,
gboolean current,
gboolean commit_to_table
);
void koto_playlist_apply_model(
KotoPlaylist * self,
KotoPreferredModelType preferred_model
KotoPreferredPlaylistSortType preferred_model
);
void koto_playlist_commit(KotoPlaylist * self);
void koto_playlist_commit_tracks(
gpointer data,
gpointer user_data
);
gint koto_playlist_compare_track_uuids(
gconstpointer a,
gconstpointer b
);
void koto_playlist_emit_modified(KotoPlaylist * self);
gchar * koto_playlist_get_artwork(KotoPlaylist * self);
KotoPreferredModelType koto_playlist_get_current_model(KotoPlaylist * self);
KotoPreferredPlaylistSortType koto_playlist_get_current_model(KotoPlaylist * self);
guint koto_playlist_get_current_position(KotoPlaylist * self);
gint koto_playlist_get_current_position(KotoPlaylist * self);
KotoTrack * koto_playlist_get_current_track(KotoPlaylist * self);
guint koto_playlist_get_length(KotoPlaylist * self);
gboolean koto_playlist_get_is_finalized(KotoPlaylist * self);
gboolean koto_playlist_get_is_hidden(KotoPlaylist * self);
gchar * koto_playlist_get_name(KotoPlaylist * self);
gint koto_playlist_get_position_of_track(
@ -139,12 +129,17 @@ void koto_playlist_remove_track_by_uuid(
gchar * uuid
);
void koto_playlist_set_album_uuid(
KotoPlaylist * self,
const gchar * album_uuid
);
void koto_playlist_set_artwork(
KotoPlaylist * self,
const gchar * path
);
void koto_playlist_save_state(KotoPlaylist * self);
void koto_playlist_save_current_playback_state(KotoPlaylist * self);
void koto_playlist_set_name(
KotoPlaylist * self,

View file

@ -10,21 +10,5 @@
color: $text-color-faded;
margin: 10px 0;
}
& .track-list {
& > row {
&:not(:active):not(:selected) { // Neither active nor selected, see gtk overrides
color: $text-color-bright;
&:nth-child(odd):not(:hover) {
background-color: $bg-primary;
}
&:nth-child(even), &:hover {
background-color: $bg-secondary;
}
}
}
}
}
}

View file

@ -1,6 +1,12 @@
@import 'components/album-info';
@import 'components/audiobook-view';
@import 'components/badge';
@import 'components/cover-art-button';
@import 'components/gtk-overrides';
@import 'components/track-list';
@import 'components/track-table';
@import 'components/writer-page';
@import 'pages/audiobook-library';
@import 'pages/artist-view';
@import 'pages/music-local';
@import 'pages/playlist-page';
@ -39,7 +45,8 @@ window {
// All the classes we want consistent padding applied to for its primary content
.artist-view-content, // Has the albums
.playlist-page { // Individual playlists
.playlist-page, // Individual playlists
.writer-page { // Writer page in Audiobook
padding: $itempadding;
}
}

View file

@ -46,4 +46,11 @@
}
}
}
.playerbar-secondary-controls { // Secondary controls
label { // Inner playback position label
font-size: large;
margin-right: $halvedpadding;
}
}
}

View file

@ -5,4 +5,5 @@ $palewhite: #cccccc;
$red : #FF4652;
$itempadding: 40px;
$halvedpadding: $itempadding / 2;
$halvedpadding: $itempadding / 2;
$quarterpadding: $itempadding / 4;

View file

@ -0,0 +1,30 @@
// This file contain the styling for the Album Info section
.album-info {
.album-description,
.album-narrator,
.album-title-year-combo,
.genres-tag-list {
margin-bottom: $quarterpadding
}
.album-title-year-combo {
padding-top: $halvedpadding;
}
.album-title { // Title of album
color: $text-color-faded;
font-size: 2.5em;
font-weight: bold;
}
.album-year {
margin-left: $halvedpadding;
}
.genres-tag-list { // Genres Tag List
& .label-badge:not(:last-child) {
margin-right: $halvedpadding;
}
}
}

View file

@ -0,0 +1,34 @@
// This is the styling for the Audiobook VIew
@import '../vars';
.audiobook-view {
.side-info { // Side Info
margin-right: $halvedpadding;
button,
image {
margin-bottom: $halvedpadding;
}
button { // Play / Continue Playback button
font-size: large;
font-weight: bold;
}
& > label {
font-size: large;
&:last-child {
margin-bottom: $halvedpadding;
}
}
}
.chapters-label { // Chapters label after album info
color: $text-color-faded;
font-size: x-large;
font-weight: bold;
padding: $halvedpadding 0; // Top / bottom padding
}
}

View file

@ -0,0 +1,12 @@
// This file contains the styling for our label badge
@import '../vars';
.label-badge {
color: $text-color-faded;
font-size: large;
font-weight: 900;
background-color: $bg-secondary;
border-radius: 10px;
padding: 5px 20px;
}

View file

@ -0,0 +1,19 @@
// Track List styling
@import '../vars';
.track-list {
& > row {
&:not(:active):not(:selected) { // Neither active nor selected, see gtk overrides
color: $text-color-bright;
&:nth-child(odd):not(:hover) {
background-color: $bg-primary;
}
&:nth-child(even), &:hover {
background-color: $bg-secondary;
}
}
}
}

View file

@ -0,0 +1,12 @@
// This is the styling for the writer page
@import '../vars';
.writer-page {
.writer-header { // Our writer / artist header label
color: $text-color-faded;
font-size: 4em;
font-weight: bold;
padding-bottom: $itempadding;
}
}

View file

@ -0,0 +1,36 @@
// This file contains the styling for our Audiobook Library
@import '../vars';
.audiobook-library { // Library page
.genres-banner { // Banner for genres list
.large-banner { // Large banner with art for each genre
padding: $itempadding;
.audiobook-genre-button { // Genre buttons
.koto-button {
font-size: 2em;
margin: 0.5em;
}
}
}
}
.writers-button-flow { // Flowbox of buttons for writers
padding: 0 $itempadding; // Horizontal padding of our standard item padding
flowboxchild {
padding: 0;
&:nth-child(even) {
margin: 0 0.5em;
}
.writer-button { // Writer button
color: $text-color-bright;
font-size: 1.4em;
background-color: $bg-secondary;
}
}
}
}

View file

@ -21,14 +21,6 @@
& > overlay {
margin-right: $itempadding;
}
& > box {
& > label {
font-size: xx-large;
font-weight: 900;
padding: $halvedpadding 0;
}
}
}
}
}