Compare commits
	
		
			338 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					b8bfc98618 | ||
| 
						 | 
					6e6f72a521 | ||
| 
						 | 
					a654c5d643 | ||
| 
						 | 
					c5d6aa169f | ||
| 
						 | 
					c1c67286d3 | ||
| 
						 | 
					2fb34697c7 | ||
| 
						 | 
					94b5b9f370 | ||
| 
						 | 
					a9467513e1 | ||
| 
						 | 
					17d944f6ce | ||
| 
						 | 
					0f82f18652 | ||
| 
						 | 
					3db3e577f1 | ||
| 
						 | 
					37ee821947 | ||
| 
						 | 
					916ab9a7e6 | ||
| 
						 | 
					1802cf9fd1 | ||
| 
						 | 
					1bf7d30623 | ||
| 
						 | 
					b2d89253a6 | ||
| 
						 | 
					50c1e3738a | ||
| 
						 | 
					7a939746ae | ||
| 
						 | 
					feac1a3f56 | ||
| 
						 | 
					f3c37e484e | ||
| 
						 | 
					49130c2018 | ||
| 
						 | 
					94af199c49 | ||
| 
						 | 
					2d25f6f57f | ||
| 
						 | 
					cf179ec294 | ||
| 
						 | 
					4d6f220a2f | ||
| 
						 | 
					0ffbe5b5ea | ||
| 
						 | 
					5b83c834ac | ||
| 
						 | 
					da7f32bddb | ||
| 
						 | 
					9a5eac4ea9 | ||
| 
						 | 
					6571b5d118 | ||
| 
						 | 
					12dff8e382 | ||
| 
						 | 
					c4da87a0cb | ||
| 
						 | 
					446f8f29d3 | ||
| 
						 | 
					48cc76f114 | ||
| 
						 | 
					a0892b852e | ||
| 
						 | 
					485c7805eb | ||
| 
						 | 
					23802f4489 | ||
| 
						 | 
					3fedd978a2 | ||
| 
						 | 
					a9f1bed922 | ||
| 
						 | 
					eb23788fec | ||
| 
						 | 
					f6d73555a6 | ||
| 
						 | 
					a56a709406 | ||
| 
						 | 
					5f253e66f6 | ||
| 
						 | 
					4669f7e2b9 | ||
| 
						 | 
					4c90f88704 | ||
| 
						 | 
					a7213b78d6 | ||
| 
						 | 
					719333e16e | ||
| 
						 | 
					100e471b49 | ||
| 
						 | 
					3f2016e552 | ||
| 
						 | 
					dd89ea4505 | ||
| 
						 | 
					101e12cf9a | ||
| 
						 | 
					f382808450 | ||
| 
						 | 
					0cbe3c2a93 | ||
| 
						 | 
					4f0ae28359 | ||
| 
						 | 
					6a4250f485 | ||
| 
						 | 
					3322b29e6a | ||
| 
						 | 
					33ac472601 | ||
| 
						 | 
					561d6fd478 | ||
| 
						 | 
					42a01822bf | ||
| 
						 | 
					38f1237d49 | ||
| 
						 | 
					8df77122e5 | ||
| 
						 | 
					fef6b9df80 | ||
| 
						 | 
					d52eac66db | ||
| 
						 | 
					70879f0abc | ||
| 
						 | 
					bcb393628e | ||
| 
						 | 
					18d3a5c12b | ||
| 
						 | 
					6ee3d0102b | ||
| 
						 | 
					fc9626e2f4 | ||
| 
						 | 
					3bedd94fc8 | ||
| 
						 | 
					8842650c33 | ||
| 
						 | 
					d5bf128cee | ||
| 
						 | 
					5cd86e272f | ||
| 
						 | 
					740cbe9e02 | ||
| 
						 | 
					ed890a273a | ||
| 
						 | 
					068cd559e1 | ||
| 
						 | 
					dc127f39a7 | ||
| 
						 | 
					7a99a7008c | ||
| 
						 | 
					70b451db7b | ||
| 
						 | 
					2ab03a0914 | ||
| 
						 | 
					2fa8c7d2db | ||
| 
						 | 
					7c759ba8b0 | ||
| 
						 | 
					6d9b452fde | ||
| 
						 | 
					f7eb1c9a83 | ||
| 
						 | 
					2d22e6dee4 | ||
| 
						 | 
					4587bf759d | ||
| 
						 | 
					e1e37cfe3c | ||
| 
						 | 
					381934985a | ||
| 
						 | 
					a8042885ac | ||
| 
						 | 
					a71e68db50 | ||
| 
						 | 
					1417578b3d | ||
| 
						 | 
					96befa138c | ||
| 
						 | 
					16a99804de | ||
| 
						 | 
					75a39ed279 | ||
| 
						 | 
					4d357ab77c | ||
| 
						 | 
					d4f3dd49b4 | ||
| 
						 | 
					4ec6d0555a | ||
| 
						 | 
					a6a1182c4c | ||
| 
						 | 
					a59c9c602b | ||
| 
						 | 
					0c4d824d64 | ||
| 
						 | 
					a5281856c9 | ||
| 
						 | 
					0206a46d39 | ||
| 
						 | 
					9475ef2202 | ||
| 
						 | 
					edae00e719 | ||
| 
						 | 
					fb695bc55f | ||
| 
						 | 
					23a5b8fd3c | ||
| 
						 | 
					273a93cfcf | ||
| 
						 | 
					d105985d78 | ||
| 
						 | 
					f8cfeb39e9 | ||
| 
						 | 
					d5d3982d3c | ||
| 
						 | 
					47341107ea | ||
| 
						 | 
					90eaa87a4d | ||
| 
						 | 
					b09a54b2c2 | ||
| 
						 | 
					10aec174d5 | ||
| 
						 | 
					d32ed194e8 | ||
| 
						 | 
					70d0fbd715 | ||
| 
						 | 
					302432e157 | ||
| 
						 | 
					4ab8a677dc | ||
| 
						 | 
					52e4a4c904 | ||
| 
						 | 
					a0f6932ebe | ||
| 
						 | 
					6e700dab69 | ||
| 
						 | 
					35eaed7206 | ||
| 
						 | 
					e7c963f2ce | ||
| 
						 | 
					949d72e368 | ||
| 
						 | 
					8d2a184658 | ||
| 
						 | 
					c877a32d97 | ||
| 
						 | 
					541468f0ca | ||
| 
						 | 
					d2797effa3 | ||
| 
						 | 
					1170fb1e1e | ||
| 
						 | 
					65b9b3195c | ||
| 
						 | 
					258830e913 | ||
| 
						 | 
					d91da96798 | ||
| 
						 | 
					b3897df682 | ||
| 
						 | 
					3cacb56bb7 | ||
| 
						 | 
					15a1973e28 | ||
| 
						 | 
					ad7d47a8ba | ||
| 
						 | 
					0948c607b6 | ||
| 
						 | 
					60d04052c5 | ||
| 
						 | 
					c1780ac657 | ||
| 
						 | 
					e49cf0ec38 | ||
| 
						 | 
					e1d641f684 | ||
| 
						 | 
					4efd0a9f77 | ||
| 
						 | 
					f6f8751332 | ||
| 
						 | 
					abb28593ce | ||
| 
						 | 
					115693b046 | ||
| 
						 | 
					e4b055eb6d | ||
| 
						 | 
					9866adff95 | ||
| 
						 | 
					a8b0c55818 | ||
| 
						 | 
					cac88e8be5 | ||
| 
						 | 
					e9f6a3482c | ||
| 
						 | 
					5d2e80f188 | ||
| 
						 | 
					cfd4d5b13e | ||
| 
						 | 
					06514aec63 | ||
| 
						 | 
					4ded1ae67b | ||
| 
						 | 
					1da974e3fa | ||
| 
						 | 
					94f06f0946 | ||
| 
						 | 
					d9eec8a455 | ||
| 
						 | 
					eaecbcafb2 | ||
| 
						 | 
					73b5d0a9b9 | ||
| 
						 | 
					c2d0f35e7a | ||
| 
						 | 
					ab99a57997 | ||
| 
						 | 
					c8ebaf3521 | ||
| 
						 | 
					52d00f7e30 | ||
| 
						 | 
					309491a6d8 | ||
| 
						 | 
					e7bfd32ccc | ||
| 
						 | 
					6f283b52ab | ||
| 
						 | 
					32bddfabea | ||
| 
						 | 
					1944c826bc | ||
| 
						 | 
					619bb60b26 | ||
| 
						 | 
					c549e16ed1 | ||
| 
						 | 
					01c9c4507f | ||
| 
						 | 
					8c9d7bf07e | ||
| 
						 | 
					44ef34db88 | ||
| 
						 | 
					5781f223f6 | ||
| 
						 | 
					e4c8ebe056 | ||
| 
						 | 
					76b25a1377 | ||
| 
						 | 
					ccc3ee663b | ||
| 
						 | 
					0626661764 | ||
| 
						 | 
					31db04a3ca | ||
| 
						 | 
					0c7163b9db | ||
| 
						 | 
					7d78cad8af | ||
| 
						 | 
					912530ed20 | ||
| 
						 | 
					d3f37199b9 | ||
| 
						 | 
					a4748d84b0 | ||
| 
						 | 
					8f847ec381 | ||
| 
						 | 
					3a70f09dd3 | ||
| 
						 | 
					568f63100b | ||
| 
						 | 
					3e25916b37 | ||
| 
						 | 
					5f9438dae6 | ||
| 
						 | 
					99e65c58ce | ||
| 
						 | 
					df71b07e9d | ||
| 
						 | 
					2694195215 | ||
| 
						 | 
					66450d1f3c | ||
| 
						 | 
					76efea3aa7 | ||
| 
						 | 
					7ab0dfc8ce | ||
| 
						 | 
					15ff7c4cad | ||
| 
						 | 
					9ab9b97f20 | ||
| 
						 | 
					88d92aceab | ||
| 
						 | 
					a2ce4352c8 | ||
| 
						 | 
					84f43ccde8 | ||
| 
						 | 
					38704c9cf3 | ||
| 
						 | 
					910d0ec92b | ||
| 
						 | 
					3b05c89765 | ||
| 
						 | 
					e77b3fa46f | ||
| 
						 | 
					12147f6d58 | ||
| 
						 | 
					40bc60d6ae | ||
| 
						 | 
					7778210269 | ||
| 
						 | 
					6229210d51 | ||
| 
						 | 
					5d0d5b5d97 | ||
| 
						 | 
					1aa3c1e543 | ||
| 
						 | 
					b90e32fe4e | ||
| 
						 | 
					1f4df2a64d | ||
| 
						 | 
					2efc1db6a9 | ||
| 
						 | 
					e2d4654e20 | ||
| 
						 | 
					2b8f1170a6 | ||
| 
						 | 
					5c4743441e | ||
| 
						 | 
					cb288439a4 | ||
| 
						 | 
					69f741e8a6 | ||
| 
						 | 
					4b4f47002b | ||
| 
						 | 
					615c301961 | ||
| 
						 | 
					dc07180e48 | ||
| 
						 | 
					d3b235bab5 | ||
| 
						 | 
					7c920ddebe | ||
| 
						 | 
					bbc088ae4e | ||
| 
						 | 
					fe195257d8 | ||
| 
						 | 
					57d5df8118 | ||
| 
						 | 
					59792cb0b8 | ||
| 
						 | 
					cc557c4d60 | ||
| 
						 | 
					956c5faebb | ||
| 
						 | 
					cd0396c1f1 | ||
| 
						 | 
					79f9b268bb | ||
| 
						 | 
					b45f3c8deb | ||
| 
						 | 
					f8a8de87e4 | ||
| 
						 | 
					2183f0553c | ||
| 
						 | 
					1f28790476 | ||
| 
						 | 
					c8dae95eff | ||
| 
						 | 
					547a084c7e | ||
| 
						 | 
					493677ff81 | ||
| 
						 | 
					6b430ba271 | ||
| 
						 | 
					bc6924d303 | ||
| 
						 | 
					02b00f9146 | ||
| 
						 | 
					e807ed5870 | ||
| 
						 | 
					f08944253b | ||
| 
						 | 
					792d6584b9 | ||
| 
						 | 
					7b45d01462 | ||
| 
						 | 
					5c17b2966a | ||
| 
						 | 
					0c54f29446 | ||
| 
						 | 
					9c3cf39fdd | ||
| 
						 | 
					d2fb229685 | ||
| 
						 | 
					f55bc6682f | ||
| 
						 | 
					6857286b42 | ||
| 
						 | 
					c0d5bd2048 | ||
| 
						 | 
					666e5d7904 | ||
| 
						 | 
					3613407ac5 | ||
| 
						 | 
					c32dceb4d4 | ||
| 
						 | 
					5573e78364 | ||
| 
						 | 
					807a19889f | ||
| 
						 | 
					df7242de91 | ||
| 
						 | 
					d62426f168 | ||
| 
						 | 
					1714cf3417 | ||
| 
						 | 
					1080c917be | ||
| 
						 | 
					8eb3164878 | ||
| 
						 | 
					915c5442d1 | ||
| 
						 | 
					be0360d5e8 | ||
| 
						 | 
					4d6ae6ffdd | ||
| 
						 | 
					ecee6f415b | ||
| 
						 | 
					47680f936b | ||
| 
						 | 
					2d7181105d | ||
| 
						 | 
					9bdc75524b | ||
| 
						 | 
					2f6ceb4949 | ||
| 
						 | 
					cd933aa35f | ||
| 
						 | 
					138738075b | ||
| 
						 | 
					2ee57f9b0d | ||
| 
						 | 
					5a5655b790 | ||
| 
						 | 
					b88d1e6820 | ||
| 
						 | 
					19d2864c34 | ||
| 
						 | 
					29e3a17f26 | ||
| 
						 | 
					252e9f736f | ||
| 
						 | 
					5d08988dda | ||
| 
						 | 
					47ca4246aa | ||
| 
						 | 
					f8338d4f00 | ||
| 
						 | 
					5cf6032c90 | ||
| 
						 | 
					8d8b77412d | ||
| 
						 | 
					fd9114e7e2 | ||
| 
						 | 
					a3fba2f8f7 | ||
| 
						 | 
					e2b671f1b2 | ||
| 
						 | 
					2a35fbe29e | ||
| 
						 | 
					81cde72fd0 | ||
| 
						 | 
					bf9ffba4f7 | ||
| 
						 | 
					c975d8b943 | ||
| 
						 | 
					2730f91872 | ||
| 
						 | 
					97ca85e155 | ||
| 
						 | 
					39bb4c5871 | ||
| 
						 | 
					bdceb90c59 | ||
| 
						 | 
					8bd1b5228c | ||
| 
						 | 
					a009e95afd | ||
| 
						 | 
					32aafb3572 | ||
| 
						 | 
					b577783cf0 | ||
| 
						 | 
					aa7b872a14 | ||
| 
						 | 
					c6f7f57776 | ||
| 
						 | 
					106ad08cd2 | ||
| 
						 | 
					0341ca1b6a | ||
| 
						 | 
					7581ea55db | ||
| 
						 | 
					fc9cee38d8 | ||
| 
						 | 
					b175e4128d | ||
| 
						 | 
					97b07798b0 | ||
| 
						 | 
					112fcd206d | ||
| 
						 | 
					11d1f56062 | ||
| 
						 | 
					bd840d4638 | ||
| 
						 | 
					c3d393f214 | ||
| 
						 | 
					f88fc0ca1a | ||
| 
						 | 
					fb8d8242ab | ||
| 
						 | 
					f2a3dfd700 | ||
| 
						 | 
					85f9863e0a | ||
| 
						 | 
					83572701f4 | ||
| 
						 | 
					fa7d7e9187 | ||
| 
						 | 
					f818cde32c | ||
| 
						 | 
					9da93cd887 | ||
| 
						 | 
					026e7ea32a | ||
| 
						 | 
					9659d19718 | ||
| 
						 | 
					50d35c9677 | ||
| 
						 | 
					4260e78861 | ||
| 
						 | 
					7342ae2e33 | ||
| 
						 | 
					35dbc1a90c | ||
| 
						 | 
					c7a4355153 | ||
| 
						 | 
					33a84a8ca2 | ||
| 
						 | 
					1d04490ed3 | ||
| 
						 | 
					4a30c2d79c | ||
| 
						 | 
					83072d6b9c | ||
| 
						 | 
					c779fc37eb | ||
| 
						 | 
					e08c13ad7e | ||
| 
						 | 
					2c82a6b2e0 | ||
| 
						 | 
					3929f17aef | ||
| 
						 | 
					ee39af3419 | ||
| 
						 | 
					3882a5a263 | ||
| 
						 | 
					ac06088948 | ||
| 
						 | 
					a757eebfbb | ||
| 
						 | 
					2be4f89555 | ||
| 
						 | 
					4a5c7d8261 | 
							
								
								
									
										12
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,12 +0,0 @@
 | 
			
		||||
# These are supported funding model platforms
 | 
			
		||||
 | 
			
		||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
 | 
			
		||||
patreon: # Replace with a single Patreon username
 | 
			
		||||
open_collective: # Replace with a single Open Collective username
 | 
			
		||||
ko_fi: # Replace with a single Ko-fi username
 | 
			
		||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
 | 
			
		||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
 | 
			
		||||
liberapay: MaxK
 | 
			
		||||
issuehunt: # Replace with a single IssueHunt username
 | 
			
		||||
otechie: # Replace with a single Otechie username
 | 
			
		||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
 | 
			
		||||
							
								
								
									
										4
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
								
							@@ -18,5 +18,9 @@ about: Create a bug report
 | 
			
		||||
<!-- Paste the output of "mpd --version" here -->
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Configuration
 | 
			
		||||
<!-- Paste your MPD configuration here -->
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Log
 | 
			
		||||
<!-- Paste relevant portions of the log file here (--verbose) -->
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										9
									
								
								.github/ISSUE_TEMPLATE/question.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.github/ISSUE_TEMPLATE/question.md
									
									
									
									
										vendored
									
									
								
							@@ -1,9 +0,0 @@
 | 
			
		||||
---
 | 
			
		||||
name: Question
 | 
			
		||||
about: Ask a question about MPD
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
<!-- Before you ask a question on GitHub, please read MPD's
 | 
			
		||||
documentation.  A copy is available at
 | 
			
		||||
https://www.musicpd.org/doc/html/ -->
 | 
			
		||||
## Question
 | 
			
		||||
							
								
								
									
										28
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										28
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							@@ -41,7 +41,8 @@ jobs:
 | 
			
		||||
          key: linux
 | 
			
		||||
      - name: Install dependencies
 | 
			
		||||
        run: |
 | 
			
		||||
          sudo apt install -y --no-install-recommends \
 | 
			
		||||
          sudo apt-get update
 | 
			
		||||
          sudo apt-get install -y --no-install-recommends \
 | 
			
		||||
            g++-10 libfmt-dev libboost-dev \
 | 
			
		||||
            libgtest-dev \
 | 
			
		||||
            libpcre2-dev \
 | 
			
		||||
@@ -73,19 +74,30 @@ jobs:
 | 
			
		||||
            libgcrypt20-dev
 | 
			
		||||
 | 
			
		||||
      - name: Full Build
 | 
			
		||||
        uses: BSFishy/meson-build@v1.0.3
 | 
			
		||||
        with:
 | 
			
		||||
          action: build
 | 
			
		||||
          directory: output/full
 | 
			
		||||
          setup-options: -Ddocumentation=disabled -Dtest=true -Dsystemd=enabled -Dpcre=enabled
 | 
			
		||||
          options: --verbose
 | 
			
		||||
          meson-version: 0.56.0
 | 
			
		||||
 | 
			
		||||
      - name: Unit Tests
 | 
			
		||||
        uses: BSFishy/meson-build@v1.0.3
 | 
			
		||||
        with:
 | 
			
		||||
          action: test
 | 
			
		||||
          directory: output/full
 | 
			
		||||
          setup-options: -Ddocumentation=disabled -Dtest=true -Dsystemd=enabled -Dpcre=enabled
 | 
			
		||||
          options: --verbose
 | 
			
		||||
          meson-version: 0.56.0
 | 
			
		||||
 | 
			
		||||
      - name: Mini Build
 | 
			
		||||
        uses: BSFishy/meson-build@v1.0.3
 | 
			
		||||
        with:
 | 
			
		||||
          action: test
 | 
			
		||||
          action: build
 | 
			
		||||
          directory: output/mini
 | 
			
		||||
          setup-options: -Dbuildtype=minsize -Dauto_features=disabled -Dtest=true -Ddaemon=false -Dinotify=false -Depoll=false -Deventfd=false -Dsignalfd=false -Dtcp=false -Ddsd=false -Ddatabase=false -Dneighbor=false -Dcue=false -Dfifo=false -Dhttpd=false -Dpipe=false -Drecorder=false -Dsnapcast=false
 | 
			
		||||
          options: --verbose
 | 
			
		||||
          meson-version: 0.56.0
 | 
			
		||||
 | 
			
		||||
  build-macos:
 | 
			
		||||
@@ -124,10 +136,20 @@ jobs:
 | 
			
		||||
            wavpack \
 | 
			
		||||
            libmpdclient
 | 
			
		||||
 | 
			
		||||
      - name: Meson Build
 | 
			
		||||
      - name: Build
 | 
			
		||||
        uses: BSFishy/meson-build@v1.0.3
 | 
			
		||||
        with:
 | 
			
		||||
          action: build
 | 
			
		||||
          directory: output
 | 
			
		||||
          setup-options: -Ddocumentation=disabled -Dtest=true
 | 
			
		||||
          options: --verbose
 | 
			
		||||
          meson-version: 0.56.0
 | 
			
		||||
 | 
			
		||||
      - name: Unit Tests
 | 
			
		||||
        uses: BSFishy/meson-build@v1.0.3
 | 
			
		||||
        with:
 | 
			
		||||
          action: test
 | 
			
		||||
          directory: output
 | 
			
		||||
          setup-options: -Ddocumentation=disabled -Dtest=true
 | 
			
		||||
          options: --verbose
 | 
			
		||||
          meson-version: 0.56.0
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										118
									
								
								NEWS
									
									
									
									
									
								
							
							
						
						
									
										118
									
								
								NEWS
									
									
									
									
									
								
							@@ -1,3 +1,121 @@
 | 
			
		||||
ver 0.23.15 (2023/12/20)
 | 
			
		||||
* decoder
 | 
			
		||||
  - ffmpeg: fix build failure with FFmpeg 6.1
 | 
			
		||||
* output
 | 
			
		||||
  - alsa: limit buffer time to 2 seconds
 | 
			
		||||
 | 
			
		||||
ver 0.23.14 (2023/10/08)
 | 
			
		||||
* decoder
 | 
			
		||||
  - flac: fix scanning files with non-ASCII names on Windows
 | 
			
		||||
  - mad: fix calculation of LAME peak values
 | 
			
		||||
* mixer
 | 
			
		||||
  - wasapi: fix problem setting volume
 | 
			
		||||
* more libfmt 10 fixes
 | 
			
		||||
* fix auto-detected systemd unit directory
 | 
			
		||||
* Android
 | 
			
		||||
  - require Android 7 or newer
 | 
			
		||||
 | 
			
		||||
ver 0.23.13 (2023/05/22)
 | 
			
		||||
* input
 | 
			
		||||
  - curl: fix busy loop after connection failed
 | 
			
		||||
  - curl: hide "404" log messages for non-existent ".mpdignore" files
 | 
			
		||||
* archive
 | 
			
		||||
  - zzip: fix crash bug
 | 
			
		||||
* database
 | 
			
		||||
  - simple: reveal hidden songs after deleting containing CUE
 | 
			
		||||
* decoder
 | 
			
		||||
  - ffmpeg: reorder to a lower priority than "gme"
 | 
			
		||||
  - gme: require GME 0.6 or later
 | 
			
		||||
* output
 | 
			
		||||
  - pipewire: fix corruption bug due to missing lock
 | 
			
		||||
* Linux
 | 
			
		||||
  - shut down if parent process dies in --no-daemon mode
 | 
			
		||||
  - determine systemd unit directories via pkg-config
 | 
			
		||||
* support libfmt 10
 | 
			
		||||
 | 
			
		||||
ver 0.23.12 (2023/01/17)
 | 
			
		||||
* input
 | 
			
		||||
  - curl: require CURL 7.55.0 or later
 | 
			
		||||
* decoder
 | 
			
		||||
  - mad: fix integer underflow with very small files
 | 
			
		||||
* tags
 | 
			
		||||
  - fix crash bug due to race condition
 | 
			
		||||
* output
 | 
			
		||||
  - pipewire: adjust to PipeWire 0.3.64 API change
 | 
			
		||||
* fix build failures with GCC 13
 | 
			
		||||
 | 
			
		||||
ver 0.23.11 (2022/11/28)
 | 
			
		||||
* database
 | 
			
		||||
  - simple: move default database to ~/.cache/mpd/db from ~/.cache/mpd.db
 | 
			
		||||
  - simple: default "cache_directory" to ~/.cache/mpd/mounts
 | 
			
		||||
* macOS: fix build failure "no archive members specified"
 | 
			
		||||
* Windows
 | 
			
		||||
  - fix crash bug (stack buffer overflow) after I/O errors
 | 
			
		||||
  - fix path traversal bug because backslash was allowed in playlist names
 | 
			
		||||
* Android/Windows
 | 
			
		||||
  - update OpenSSL to 3.0.7
 | 
			
		||||
  - re-enable CURL's verbose error strings
 | 
			
		||||
 | 
			
		||||
ver 0.23.10 (2022/10/14)
 | 
			
		||||
* storage
 | 
			
		||||
  - curl: fix file time stamps
 | 
			
		||||
* decoder
 | 
			
		||||
  - ffmpeg: fix libfmt 9 compiler warning
 | 
			
		||||
* encoder
 | 
			
		||||
  - flac: fix failure when libFLAC is built without Ogg support
 | 
			
		||||
* output
 | 
			
		||||
  - alsa: fix crash bug
 | 
			
		||||
* Windows
 | 
			
		||||
  - log to stdout by default, don't require "log_file" setting
 | 
			
		||||
 | 
			
		||||
ver 0.23.9 (2022/08/18)
 | 
			
		||||
* input
 | 
			
		||||
  - cdio_paranoia: add options "mode" and "skip"
 | 
			
		||||
* decoder
 | 
			
		||||
  - ffmpeg: support FFmpeg 5.1
 | 
			
		||||
* filter
 | 
			
		||||
  - replay gain: fix delayed volume display with handler=mixer
 | 
			
		||||
* output
 | 
			
		||||
  - pipewire: set app icon
 | 
			
		||||
* fix bogus volume levels with multiple partitions
 | 
			
		||||
* improve iconv detection
 | 
			
		||||
* macOS: fix macOS 10 build problem (0.23.8 regression)
 | 
			
		||||
* Android
 | 
			
		||||
  - load mpd.conf from app data directory
 | 
			
		||||
 | 
			
		||||
ver 0.23.8 (2022/07/09)
 | 
			
		||||
* storage
 | 
			
		||||
  - curl: fix crash if web server does not understand WebDAV
 | 
			
		||||
* input
 | 
			
		||||
  - cdio_paranoia: fix crash if no drive was found
 | 
			
		||||
  - cdio_paranoia: faster cancellation
 | 
			
		||||
  - cdio_paranoia: don't scan for replay gain tags
 | 
			
		||||
  - pipewire: fix playback of very short tracks
 | 
			
		||||
  - pipewire: drop all buffers before manual song change
 | 
			
		||||
  - pipewire: fix stuttering after manual song change
 | 
			
		||||
  - snapcast: fix busy loop while paused
 | 
			
		||||
  - snapcast: fix stuttering after resuming playback
 | 
			
		||||
* mixer
 | 
			
		||||
  - better error messages
 | 
			
		||||
  - alsa: fix setting volume before playback starts
 | 
			
		||||
  - pipewire: fix crash bug
 | 
			
		||||
  - pipewire: fix volume change events with PipeWire 0.3.53
 | 
			
		||||
  - pipewire: don't force initial volume=100%
 | 
			
		||||
* support libfmt 9
 | 
			
		||||
 | 
			
		||||
ver 0.23.7 (2022/05/09)
 | 
			
		||||
* database
 | 
			
		||||
  - upnp: support pupnp 1.14
 | 
			
		||||
* decoder
 | 
			
		||||
  - ffmpeg: fix HLS seeking
 | 
			
		||||
  - opus: fix missing song length on high-latency files
 | 
			
		||||
* output
 | 
			
		||||
  - shout: require at least libshout 2.4.0
 | 
			
		||||
* mixer
 | 
			
		||||
  - pipewire: fix volume restore
 | 
			
		||||
  - software: update volume of disabled outputs
 | 
			
		||||
* support libiconv
 | 
			
		||||
 | 
			
		||||
ver 0.23.6 (2022/03/14)
 | 
			
		||||
* protocol
 | 
			
		||||
  - support filename "cover.webp" for "albumart" command
 | 
			
		||||
 
 | 
			
		||||
@@ -2,10 +2,10 @@
 | 
			
		||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
          package="org.musicpd"
 | 
			
		||||
          android:installLocation="auto"
 | 
			
		||||
          android:versionCode="65"
 | 
			
		||||
          android:versionName="0.23.5">
 | 
			
		||||
          android:versionCode="73"
 | 
			
		||||
          android:versionName="0.23.15">
 | 
			
		||||
 | 
			
		||||
  <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29"/>
 | 
			
		||||
  <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="30"/>
 | 
			
		||||
 | 
			
		||||
  <uses-feature android:name="android.software.leanback"
 | 
			
		||||
                android:required="false" />
 | 
			
		||||
@@ -19,6 +19,7 @@
 | 
			
		||||
  <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
 | 
			
		||||
 | 
			
		||||
  <application android:allowBackup="true"
 | 
			
		||||
               android:debuggable="true"
 | 
			
		||||
               android:requestLegacyExternalStorage="true"
 | 
			
		||||
               android:icon="@drawable/icon"
 | 
			
		||||
               android:banner="@drawable/icon"
 | 
			
		||||
 
 | 
			
		||||
@@ -12,18 +12,30 @@ unsigned_apk = custom_target(
 | 
			
		||||
  ],
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
aligned_apk = custom_target(
 | 
			
		||||
  'mpd-aligned.apk',
 | 
			
		||||
  output: 'mpd-aligned.apk',
 | 
			
		||||
  input: unsigned_apk,
 | 
			
		||||
  command: [
 | 
			
		||||
    android_zipalign,
 | 
			
		||||
    '-f', '4',
 | 
			
		||||
    '@INPUT@', '@OUTPUT@',
 | 
			
		||||
  ],
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if get_option('android_debug_keystore') != ''
 | 
			
		||||
  debug_apk = custom_target(
 | 
			
		||||
    'mpd-debug.apk',
 | 
			
		||||
    output: 'mpd-debug.apk',
 | 
			
		||||
    input: unsigned_apk,
 | 
			
		||||
    input: aligned_apk,
 | 
			
		||||
    command: [
 | 
			
		||||
      jarsigner,
 | 
			
		||||
      '-keystore', get_option('android_debug_keystore'),
 | 
			
		||||
      '-storepass', 'android',
 | 
			
		||||
      '-signedjar', '@OUTPUT@',
 | 
			
		||||
      '@INPUT@',
 | 
			
		||||
      'androiddebugkey',
 | 
			
		||||
      apksigner, 'sign',
 | 
			
		||||
      '--in', '@INPUT@',
 | 
			
		||||
      '--out', '@OUTPUT@',
 | 
			
		||||
      '--debuggable-apk-permitted',
 | 
			
		||||
      '-ks', get_option('android_debug_keystore'),
 | 
			
		||||
      '--ks-key-alias', 'androiddebugkey',
 | 
			
		||||
      '--ks-pass', 'pass:android',
 | 
			
		||||
    ],
 | 
			
		||||
    build_by_default: true
 | 
			
		||||
  )
 | 
			
		||||
@@ -31,29 +43,16 @@ endif
 | 
			
		||||
 | 
			
		||||
if get_option('android_keystore') != '' and get_option('android_keyalias') != '' and get_option('android_keypass') != ''
 | 
			
		||||
  unaligned_apk = custom_target(
 | 
			
		||||
    'mpd-unaligned.apk',
 | 
			
		||||
    output: 'mpd-unaligned.apk',
 | 
			
		||||
    input: unsigned_apk,
 | 
			
		||||
    command: [
 | 
			
		||||
      jarsigner,
 | 
			
		||||
      '-digestalg', 'SHA1', '-sigalg', 'MD5withRSA',
 | 
			
		||||
      '-keystore', get_option('android_keystore'),
 | 
			
		||||
      '-storepass', get_option('android_keypass'),
 | 
			
		||||
      '-signedjar', '@OUTPUT@',
 | 
			
		||||
      '@INPUT@',
 | 
			
		||||
      get_option('android_keyalias'),
 | 
			
		||||
    ],
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  apk = custom_target(
 | 
			
		||||
    'mpd.apk',
 | 
			
		||||
    output: 'mpd.apk',
 | 
			
		||||
    input: unaligned_apk,
 | 
			
		||||
    input: aligned_apk,
 | 
			
		||||
    command: [
 | 
			
		||||
      android_zipalign,
 | 
			
		||||
      '-f', '4',
 | 
			
		||||
      '@INPUT@', '@OUTPUT@',
 | 
			
		||||
      apksigner, 'sign',
 | 
			
		||||
      '--in', '@INPUT@',
 | 
			
		||||
      '--out', '@OUTPUT@',
 | 
			
		||||
      '-ks', get_option('android_keystore'),
 | 
			
		||||
      '--ks-key-alias', get_option('android_keyalias'),
 | 
			
		||||
      '--ks-pass', 'pass:' + get_option('android_keypass'),
 | 
			
		||||
    ],
 | 
			
		||||
    build_by_default: true
 | 
			
		||||
  )
 | 
			
		||||
endif
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										129
									
								
								android/build.py
									
									
									
									
									
								
							
							
						
						
									
										129
									
								
								android/build.py
									
									
									
									
									
								
							@@ -1,4 +1,4 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
#!/usr/bin/env -S python3 -u
 | 
			
		||||
 | 
			
		||||
import os, os.path
 | 
			
		||||
import sys, subprocess
 | 
			
		||||
@@ -20,130 +20,13 @@ if not os.path.isdir(ndk_path):
 | 
			
		||||
    print("NDK not found in", ndk_path, file=sys.stderr)
 | 
			
		||||
    sys.exit(1)
 | 
			
		||||
 | 
			
		||||
android_abis = {
 | 
			
		||||
    'armeabi-v7a': {
 | 
			
		||||
        'arch': 'arm-linux-androideabi',
 | 
			
		||||
        'ndk_arch': 'arm',
 | 
			
		||||
        'llvm_triple': 'armv7-linux-androideabi',
 | 
			
		||||
        'cflags': '-fpic -mfpu=neon -mfloat-abi=softfp',
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    'arm64-v8a': {
 | 
			
		||||
        'arch': 'aarch64-linux-android',
 | 
			
		||||
        'ndk_arch': 'arm64',
 | 
			
		||||
        'llvm_triple': 'aarch64-linux-android',
 | 
			
		||||
        'cflags': '-fpic',
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    'x86': {
 | 
			
		||||
        'arch': 'i686-linux-android',
 | 
			
		||||
        'ndk_arch': 'x86',
 | 
			
		||||
        'llvm_triple': 'i686-linux-android',
 | 
			
		||||
        'cflags': '-fPIC -march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32',
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    'x86_64': {
 | 
			
		||||
        'arch': 'x86_64-linux-android',
 | 
			
		||||
        'ndk_arch': 'x86_64',
 | 
			
		||||
        'llvm_triple': 'x86_64-linux-android',
 | 
			
		||||
        'cflags': '-fPIC -m64',
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# select the NDK target
 | 
			
		||||
abi_info = android_abis[android_abi]
 | 
			
		||||
arch = abi_info['arch']
 | 
			
		||||
 | 
			
		||||
# the path to the MPD sources
 | 
			
		||||
mpd_path = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]) or '.', '..'))
 | 
			
		||||
sys.path[0] = os.path.join(mpd_path, 'python')
 | 
			
		||||
 | 
			
		||||
# output directories
 | 
			
		||||
from build.dirs import lib_path, tarball_path, src_path
 | 
			
		||||
from build.meson import configure as run_meson
 | 
			
		||||
 | 
			
		||||
arch_path = os.path.join(lib_path, arch)
 | 
			
		||||
build_path = os.path.join(arch_path, 'build')
 | 
			
		||||
 | 
			
		||||
# build host configuration
 | 
			
		||||
build_arch = 'linux-x86_64'
 | 
			
		||||
 | 
			
		||||
# set up the NDK toolchain
 | 
			
		||||
 | 
			
		||||
class AndroidNdkToolchain:
 | 
			
		||||
    def __init__(self, tarball_path, src_path, build_path,
 | 
			
		||||
                 use_cxx):
 | 
			
		||||
        self.tarball_path = tarball_path
 | 
			
		||||
        self.src_path = src_path
 | 
			
		||||
        self.build_path = build_path
 | 
			
		||||
 | 
			
		||||
        ndk_arch = abi_info['ndk_arch']
 | 
			
		||||
        android_api_level = '21'
 | 
			
		||||
 | 
			
		||||
        install_prefix = os.path.join(arch_path, 'root')
 | 
			
		||||
 | 
			
		||||
        self.arch = arch
 | 
			
		||||
        self.actual_arch = arch
 | 
			
		||||
        self.install_prefix = install_prefix
 | 
			
		||||
 | 
			
		||||
        llvm_path = os.path.join(ndk_path, 'toolchains', 'llvm', 'prebuilt', build_arch)
 | 
			
		||||
        llvm_triple = abi_info['llvm_triple'] + android_api_level
 | 
			
		||||
 | 
			
		||||
        common_flags = '-Os -g'
 | 
			
		||||
        common_flags += ' ' + abi_info['cflags']
 | 
			
		||||
 | 
			
		||||
        llvm_bin = os.path.join(llvm_path, 'bin')
 | 
			
		||||
        self.cc = os.path.join(llvm_bin, 'clang')
 | 
			
		||||
        self.cxx = os.path.join(llvm_bin, 'clang++')
 | 
			
		||||
        common_flags += ' -target ' + llvm_triple
 | 
			
		||||
 | 
			
		||||
        common_flags += ' -fvisibility=hidden -fdata-sections -ffunction-sections'
 | 
			
		||||
 | 
			
		||||
        self.ar = os.path.join(llvm_bin, 'llvm-ar')
 | 
			
		||||
        self.ranlib = os.path.join(llvm_bin, 'llvm-ranlib')
 | 
			
		||||
        self.nm = os.path.join(llvm_bin, 'llvm-nm')
 | 
			
		||||
        self.strip = os.path.join(llvm_bin, 'llvm-strip')
 | 
			
		||||
 | 
			
		||||
        self.cflags = common_flags
 | 
			
		||||
        self.cxxflags = common_flags
 | 
			
		||||
        self.cppflags = ' -isystem ' + os.path.join(install_prefix, 'include')
 | 
			
		||||
        self.ldflags = ' -L' + os.path.join(install_prefix, 'lib') + \
 | 
			
		||||
            ' -Wl,--exclude-libs=ALL' + \
 | 
			
		||||
            ' ' + common_flags
 | 
			
		||||
        self.ldflags = common_flags
 | 
			
		||||
        self.libs = ''
 | 
			
		||||
 | 
			
		||||
        self.is_arm = ndk_arch == 'arm'
 | 
			
		||||
        self.is_armv7 = self.is_arm and 'armv7' in self.cflags
 | 
			
		||||
        self.is_aarch64 = ndk_arch == 'arm64'
 | 
			
		||||
        self.is_windows = False
 | 
			
		||||
 | 
			
		||||
        libstdcxx_flags = ''
 | 
			
		||||
        libstdcxx_cxxflags = ''
 | 
			
		||||
        libstdcxx_ldflags = ''
 | 
			
		||||
        libstdcxx_libs = '-static-libstdc++'
 | 
			
		||||
 | 
			
		||||
        if self.is_armv7:
 | 
			
		||||
            # On 32 bit ARM, clang generates no ".eh_frame" section;
 | 
			
		||||
            # instead, the LLVM unwinder library is used for unwinding
 | 
			
		||||
            # the stack after a C++ exception was thrown
 | 
			
		||||
            libstdcxx_libs += ' -lunwind'
 | 
			
		||||
 | 
			
		||||
        if use_cxx:
 | 
			
		||||
            self.cxxflags += ' ' + libstdcxx_cxxflags
 | 
			
		||||
            self.ldflags += ' ' + libstdcxx_ldflags
 | 
			
		||||
            self.libs += ' ' + libstdcxx_libs
 | 
			
		||||
 | 
			
		||||
        self.env = dict(os.environ)
 | 
			
		||||
 | 
			
		||||
        # redirect pkg-config to use our root directory instead of the
 | 
			
		||||
        # default one on the build host
 | 
			
		||||
        import shutil
 | 
			
		||||
        bin_dir = os.path.join(install_prefix, 'bin')
 | 
			
		||||
        os.makedirs(bin_dir, exist_ok=True)
 | 
			
		||||
        self.pkg_config = shutil.copy(os.path.join(mpd_path, 'build', 'pkg-config.sh'),
 | 
			
		||||
                                      os.path.join(bin_dir, 'pkg-config'))
 | 
			
		||||
        self.env['PKG_CONFIG'] = self.pkg_config
 | 
			
		||||
from build.toolchain import AndroidNdkToolchain
 | 
			
		||||
 | 
			
		||||
# a list of third-party libraries to be used by MPD on Android
 | 
			
		||||
from build.libs import *
 | 
			
		||||
@@ -165,13 +48,17 @@ thirdparty_libs = [
 | 
			
		||||
 | 
			
		||||
# build the third-party libraries
 | 
			
		||||
for x in thirdparty_libs:
 | 
			
		||||
    toolchain = AndroidNdkToolchain(tarball_path, src_path, build_path,
 | 
			
		||||
    toolchain = AndroidNdkToolchain(mpd_path, lib_path,
 | 
			
		||||
                                    tarball_path, src_path,
 | 
			
		||||
                                    ndk_path, android_abi,
 | 
			
		||||
                                    use_cxx=x.use_cxx)
 | 
			
		||||
    if not x.is_installed(toolchain):
 | 
			
		||||
        x.build(toolchain)
 | 
			
		||||
 | 
			
		||||
# configure and build MPD
 | 
			
		||||
toolchain = AndroidNdkToolchain(tarball_path, src_path, build_path,
 | 
			
		||||
toolchain = AndroidNdkToolchain(mpd_path, lib_path,
 | 
			
		||||
                                tarball_path, src_path,
 | 
			
		||||
                                ndk_path, android_abi,
 | 
			
		||||
                                use_cxx=True)
 | 
			
		||||
 | 
			
		||||
configure_args += [
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										54
									
								
								android/gdb.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										54
									
								
								android/gdb.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,54 @@
 | 
			
		||||
#!/bin/sh
 | 
			
		||||
 | 
			
		||||
# This script need the following modification in ANDROID_NDK in order to attach
 | 
			
		||||
# to the good :main pid
 | 
			
		||||
#--- a/prebuilt/linux-x86_64/bin/ndk-gdb.py
 | 
			
		||||
#+++ b/prebuilt/linux-x86_64/bin/ndk-gdb.py
 | 
			
		||||
#@@ -669,7 +669,7 @@
 | 
			
		||||
#             log("Sleeping for {} seconds.".format(args.delay))
 | 
			
		||||
#             time.sleep(args.delay)
 | 
			
		||||
#
 | 
			
		||||
#-    pids = gdbrunner.get_pids(device, pkg_name)
 | 
			
		||||
#+    pids = gdbrunner.get_pids(device, pkg_name + ":main")
 | 
			
		||||
#     if len(pids) == 0:
 | 
			
		||||
#         error("Failed to find running process '{}'".format(pkg_name))
 | 
			
		||||
#     if len(pids) > 1:
 | 
			
		||||
 | 
			
		||||
SCRIPT_PATH=$(dirname $0)
 | 
			
		||||
BUILD_PATH="`pwd`"
 | 
			
		||||
TMP_PATH="$BUILD_PATH/gdb"
 | 
			
		||||
NDK_GDB_ARGS="--force"
 | 
			
		||||
ANDROID_NDK=$1
 | 
			
		||||
 | 
			
		||||
if [ ! -f $ANDROID_NDK/source.properties ];then
 | 
			
		||||
    echo "usage: $0 ANDROID_NDK"
 | 
			
		||||
    exit 1
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
if [ ! -f $BUILD_PATH/libmpd.so ];then
 | 
			
		||||
    echo "This script need to be executed from the android build directory"
 | 
			
		||||
    exit 1
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
rm -rf "$TMP_PATH"
 | 
			
		||||
mkdir -p "$TMP_PATH"
 | 
			
		||||
 | 
			
		||||
ANDROID_MANIFEST="$SCRIPT_PATH/AndroidManifest.xml"
 | 
			
		||||
ABI=`ls "$BUILD_PATH/android/apk/apk/lib" --sort=time | head -n 1`
 | 
			
		||||
 | 
			
		||||
if [ ! -f "$ANDROID_MANIFEST" -o "$ABI" = "" ]; then
 | 
			
		||||
    echo "Invalid manifest/ABI, did you try building first ?"
 | 
			
		||||
    exit 1
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
mkdir -p "$TMP_PATH"/jni
 | 
			
		||||
touch "$TMP_PATH"/jni/Android.mk
 | 
			
		||||
echo "APP_ABI := $ABI" > "$TMP_PATH"/jni/Application.mk
 | 
			
		||||
 | 
			
		||||
DEST=obj/local/$ABI
 | 
			
		||||
mkdir -p "$TMP_PATH/$DEST"
 | 
			
		||||
 | 
			
		||||
cp "$BUILD_PATH/libmpd.so" "$TMP_PATH/$DEST"
 | 
			
		||||
cp "$ANDROID_MANIFEST" "$TMP_PATH"
 | 
			
		||||
 | 
			
		||||
(cd "$TMP_PATH" && bash $ANDROID_NDK/ndk-gdb $NDK_GDB_ARGS)
 | 
			
		||||
@@ -17,7 +17,7 @@ android_dx = join_paths(android_build_tools_dir, 'dx')
 | 
			
		||||
android_zipalign = join_paths(android_build_tools_dir, 'zipalign')
 | 
			
		||||
 | 
			
		||||
javac = find_program('javac')
 | 
			
		||||
jarsigner = find_program('jarsigner')
 | 
			
		||||
apksigner = find_program('apksigner')
 | 
			
		||||
rsvg_convert = find_program('rsvg-convert')
 | 
			
		||||
convert = find_program('convert')
 | 
			
		||||
zip = find_program('zip')
 | 
			
		||||
 
 | 
			
		||||
@@ -38,7 +38,10 @@ author = 'Max Kellermann'
 | 
			
		||||
# built documents.
 | 
			
		||||
#
 | 
			
		||||
# The short X.Y version.
 | 
			
		||||
version = '0.23.6'
 | 
			
		||||
with open('../meson.build') as f:
 | 
			
		||||
    import re
 | 
			
		||||
    version = re.match(r"project\([^\)]*\bversion:\s*'([^']+)'",
 | 
			
		||||
                       f.read(4096)).group(1)
 | 
			
		||||
# The full version, including alpha/beta/rc tags.
 | 
			
		||||
#release = version + '~git'
 | 
			
		||||
 | 
			
		||||
@@ -47,7 +50,7 @@ version = '0.23.6'
 | 
			
		||||
#
 | 
			
		||||
# This is also used if you do content translation via gettext catalogs.
 | 
			
		||||
# Usually you set "language" from the command line for these cases.
 | 
			
		||||
language = None
 | 
			
		||||
language = "en"
 | 
			
		||||
 | 
			
		||||
# There are two options for replacing |today|: either, you set today to some
 | 
			
		||||
# non-false value, then it is used:
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,7 @@ Some example code:
 | 
			
		||||
 | 
			
		||||
.. code-block:: c
 | 
			
		||||
 | 
			
		||||
    int
 | 
			
		||||
    Foo(const char *abc, int xyz)
 | 
			
		||||
    {
 | 
			
		||||
        if (abc == nullptr) {
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,12 @@ Music Player Daemon
 | 
			
		||||
   client
 | 
			
		||||
   protocol
 | 
			
		||||
 | 
			
		||||
.. toctree::
 | 
			
		||||
   :maxdepth: 1
 | 
			
		||||
   :caption: man pages:
 | 
			
		||||
 | 
			
		||||
   mpd.1
 | 
			
		||||
   mpd.conf.5
 | 
			
		||||
 | 
			
		||||
Indices and tables
 | 
			
		||||
==================
 | 
			
		||||
 
 | 
			
		||||
@@ -181,7 +181,7 @@
 | 
			
		||||
#
 | 
			
		||||
#database {
 | 
			
		||||
#       plugin "simple"
 | 
			
		||||
#       path "~/.local/share/mpd/db
 | 
			
		||||
#       path "~/.local/share/mpd/db"
 | 
			
		||||
#       cache_directory "~/.local/share/mpd/cache"
 | 
			
		||||
#}
 | 
			
		||||
#
 | 
			
		||||
@@ -314,6 +314,7 @@ input {
 | 
			
		||||
##	device		"Digital Audio (S/PDIF) (High Definition Audio Device)" # optional
 | 
			
		||||
#		or
 | 
			
		||||
##	device		"0"		# optional
 | 
			
		||||
##	mixer_type	"hardware"	# optional
 | 
			
		||||
## Exclusive mode blocks all other audio source, and get best audio quality without resampling.
 | 
			
		||||
##	exclusive	"no"		# optional
 | 
			
		||||
## Enumerate all devices in log.
 | 
			
		||||
 
 | 
			
		||||
@@ -206,6 +206,11 @@ Plays audio CDs using libcdio. The URI has the form: "cdda://[DEVICE][/TRACK]".
 | 
			
		||||
     - If the CD drive does not specify a byte order, MPD assumes it is the CPU's native byte order. This setting allows overriding this.
 | 
			
		||||
   * - **speed N**
 | 
			
		||||
     - Request CDParanoia cap the extraction speed to Nx normal CD audio rotation speed, keeping the drive quiet.
 | 
			
		||||
   * - **mode disable|overlap|full**
 | 
			
		||||
     - Set the paranoia mode; ``disable`` means no fixups, ``overlap``
 | 
			
		||||
       performs overlapped reads, and ``full`` enables all options.
 | 
			
		||||
   * - **skip yes|no**
 | 
			
		||||
     - If set to ``no``, then never skip failed reads.
 | 
			
		||||
 | 
			
		||||
curl
 | 
			
		||||
----
 | 
			
		||||
@@ -214,8 +219,9 @@ Opens remote files or streams over HTTP using libcurl.
 | 
			
		||||
 | 
			
		||||
Note that unless overridden by the below settings (e.g. by setting
 | 
			
		||||
them to a blank value), general curl configuration from environment
 | 
			
		||||
variables such as ``http_proxy`` or specified in :file:`~/.curlrc`
 | 
			
		||||
will be in effect.
 | 
			
		||||
variables such as ``http_proxy`` will be in effect.
 | 
			
		||||
 | 
			
		||||
User name and password are read from an optional :file:`~/.netrc`, :file:`~/.curlrc` is not read.
 | 
			
		||||
 | 
			
		||||
.. list-table::
 | 
			
		||||
   :widths: 20 80
 | 
			
		||||
@@ -1109,7 +1115,7 @@ Connect to a `PipeWire <https://pipewire.org/>`_ server.  Requires
 | 
			
		||||
   * - **target NAME**
 | 
			
		||||
     - Link to the given target.  If not specified, let the PipeWire
 | 
			
		||||
       manager select a target.  To get a list of available targets,
 | 
			
		||||
       type ``pw-cli dump short Node``
 | 
			
		||||
       type ``pw-cli ls Node``
 | 
			
		||||
   * - **remote NAME**
 | 
			
		||||
     - The name of the remote to connect to.  The default is
 | 
			
		||||
       ``pipewire-0``.
 | 
			
		||||
 
 | 
			
		||||
@@ -545,6 +545,13 @@ Playback options
 | 
			
		||||
    Sets repeat state to ``STATE``,
 | 
			
		||||
    ``STATE`` should be 0 or 1.
 | 
			
		||||
 | 
			
		||||
    If enabled, MPD keeps repeating the whole queue (:ref:`single mode
 | 
			
		||||
    <command_single>` disabled) or the current song (:ref:`single mode
 | 
			
		||||
    <command_single>` enabled).
 | 
			
		||||
 | 
			
		||||
    If :ref:`random mode <command_random>` is also enabled, the
 | 
			
		||||
    playback order will be shuffled each time the queue gets repeated.
 | 
			
		||||
 | 
			
		||||
.. _command_setvol:
 | 
			
		||||
 | 
			
		||||
:command:`setvol {VOL}`
 | 
			
		||||
@@ -684,7 +691,8 @@ Song ids on the other hand are stable: an id is assigned to a song
 | 
			
		||||
when it is added, and will stay the same, no matter how much it is
 | 
			
		||||
moved around.  Adding the same song twice will assign different ids to
 | 
			
		||||
them, and a deleted-and-readded song will have a new id.  This way, a
 | 
			
		||||
client can always be sure the correct song is being used.
 | 
			
		||||
client can always be sure the correct song is being used.  Song ids are not
 | 
			
		||||
preserved across :program:`MPD` restarts.
 | 
			
		||||
 | 
			
		||||
Many commands come in two flavors, one for each address type.
 | 
			
		||||
Whenever possible, ids should be used.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										31
									
								
								doc/user.rst
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								doc/user.rst
									
									
									
									
									
								
							@@ -36,7 +36,9 @@ Installing on Android
 | 
			
		||||
 | 
			
		||||
An experimental Android build is available on Google Play. After installing and launching it, :program:`MPD` will scan the music in your Music directory and you can control it as usual with a :program:`MPD` client.
 | 
			
		||||
 | 
			
		||||
If you need to tweak the configuration, you can create a file called :file:`mpd.conf` on the data partition (the directory which is returned by Android's :dfn:`getExternalStorageDirectory()` API function). 
 | 
			
		||||
If you need to tweak the configuration, you can create a file called
 | 
			
		||||
:file:`mpd.conf` in MPD's data directory on the external storage
 | 
			
		||||
(usually :file:`Android/data/org.musicpd/files/mpd.conf`).
 | 
			
		||||
 | 
			
		||||
ALSA is not available on Android; only the :ref:`OpenSL ES
 | 
			
		||||
<sles_output>` output plugin can be used for local playback.
 | 
			
		||||
@@ -197,7 +199,7 @@ Compiling for Android
 | 
			
		||||
You need:
 | 
			
		||||
 | 
			
		||||
* Android SDK
 | 
			
		||||
* `Android NDK r23 <https://developer.android.com/ndk/downloads>`_
 | 
			
		||||
* `Android NDK r25b <https://developer.android.com/ndk/downloads>`_
 | 
			
		||||
* `Meson 0.56.0 <http://mesonbuild.com/>`__ and `Ninja
 | 
			
		||||
  <https://ninja-build.org/>`__
 | 
			
		||||
* cmake
 | 
			
		||||
@@ -301,7 +303,7 @@ Configuring neighbor plugins
 | 
			
		||||
----------------------------
 | 
			
		||||
 | 
			
		||||
All neighbor plugins are disabled by default to avoid unwanted
 | 
			
		||||
overhead. To enable (and configure) a plugin, add a :code:`neighbor`
 | 
			
		||||
overhead. To enable (and configure) a plugin, add a :code:`neighbors`
 | 
			
		||||
block to :file:`mpd.conf`:
 | 
			
		||||
 | 
			
		||||
.. code-block:: none
 | 
			
		||||
@@ -538,7 +540,7 @@ The following table lists the playlist_plugin options valid for all plugins:
 | 
			
		||||
 | 
			
		||||
   * - Name
 | 
			
		||||
     - Description
 | 
			
		||||
   * - **plugin**
 | 
			
		||||
   * - **name**
 | 
			
		||||
     - The name of the plugin
 | 
			
		||||
   * - **enabled yes|no**
 | 
			
		||||
     - Allows you to disable a playlist plugin without recompiling. By default, all plugins are enabled.
 | 
			
		||||
@@ -609,6 +611,11 @@ If ReplayGain is enabled, then the setting ``replaygain_preamp`` is
 | 
			
		||||
set to a value (in dB) between ``-15`` and ``15``.  This is the gain
 | 
			
		||||
applied to songs with ReplayGain tags.
 | 
			
		||||
 | 
			
		||||
On songs without ReplayGain tags, the setting
 | 
			
		||||
``replaygain_missing_preamp`` is used instead.  If this setting is not
 | 
			
		||||
configured, then no ReplayGain is applied to such songs, and they will
 | 
			
		||||
appear too loud.
 | 
			
		||||
 | 
			
		||||
ReplayGain is usually implemented with a software volume filter (which
 | 
			
		||||
prevents `Bit-perfect playback`_).  To use a hardware mixer, set
 | 
			
		||||
``replay_gain_handler`` to ``mixer`` in the ``audio_output`` section
 | 
			
		||||
@@ -654,9 +661,11 @@ MPD enables MixRamp if:
 | 
			
		||||
- Cross-fade is enabled
 | 
			
		||||
- :ref:`mixrampdelay <command_mixrampdelay>` is set to a positive
 | 
			
		||||
  value, e.g.::
 | 
			
		||||
 | 
			
		||||
    mpc mixrampdelay 1
 | 
			
		||||
- :ref:`mixrampdb <command_mixrampdb>` is set to a reasonable value,
 | 
			
		||||
  e.g.::
 | 
			
		||||
 | 
			
		||||
    mpc mixrampdb -17
 | 
			
		||||
- both songs have MixRamp tags
 | 
			
		||||
- both songs have the same audio format (or :ref:`audio_output_format`
 | 
			
		||||
@@ -1063,7 +1072,19 @@ The "music directory" is where you store your music files. :program:`MPD` stores
 | 
			
		||||
 | 
			
		||||
Depending on the size of your music collection and the speed of the storage, this can take a while.
 | 
			
		||||
 | 
			
		||||
To exclude a file from the update, create a file called :file:`.mpdignore` in its parent directory. Each line of that file may contain a list of shell wildcards. Matching files in the current directory and all subdirectories are excluded.
 | 
			
		||||
To exclude a file from the update, create a file called
 | 
			
		||||
:file:`.mpdignore` in its parent directory.  Each line of that file
 | 
			
		||||
may contain a list of shell wildcards.  Matching files (or
 | 
			
		||||
directories) in the current directory and all subdirectories are
 | 
			
		||||
excluded.  Example::
 | 
			
		||||
 | 
			
		||||
  *.opus
 | 
			
		||||
  99*
 | 
			
		||||
 | 
			
		||||
Subject to pattern matching is the file/directory name.  It is (not
 | 
			
		||||
yet) possible to match nested path names, e.g. something like
 | 
			
		||||
``foo/*.flac`` is not possible.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Mounting other storages into the music directory
 | 
			
		||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										17
									
								
								meson.build
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								meson.build
									
									
									
									
									
								
							@@ -1,7 +1,7 @@
 | 
			
		||||
project(
 | 
			
		||||
  'mpd',
 | 
			
		||||
  ['c', 'cpp'],
 | 
			
		||||
  version: '0.23.6',
 | 
			
		||||
  version: '0.23.15',
 | 
			
		||||
  meson_version: '>= 0.56.0',
 | 
			
		||||
  default_options: [
 | 
			
		||||
    'c_std=c11',
 | 
			
		||||
@@ -73,6 +73,9 @@ test_common_flags = [
 | 
			
		||||
  # clang specific warning options:
 | 
			
		||||
  '-Wunreachable-code-aggressive',
 | 
			
		||||
  '-Wused-but-marked-unused',
 | 
			
		||||
 | 
			
		||||
  # suppress bogus GCC12 warnings in libfmt headers
 | 
			
		||||
  '-Wno-stringop-overflow',
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
test_global_cxxflags = test_global_common_flags + [
 | 
			
		||||
@@ -202,7 +205,6 @@ enable_daemon = not is_windows and not is_android and get_option('daemon')
 | 
			
		||||
conf.set('ENABLE_DAEMON', enable_daemon)
 | 
			
		||||
 | 
			
		||||
conf.set('HAVE_GETPWNAM_R', compiler.has_function('getpwnam_r'))
 | 
			
		||||
conf.set('HAVE_GETPWUID_R', compiler.has_function('getpwuid_r'))
 | 
			
		||||
conf.set('HAVE_INITGROUPS', compiler.has_function('initgroups'))
 | 
			
		||||
conf.set('HAVE_FNMATCH', compiler.has_function('fnmatch'))
 | 
			
		||||
 | 
			
		||||
@@ -248,6 +250,14 @@ endif
 | 
			
		||||
 | 
			
		||||
fmt_dep = dependency('fmt', fallback: ['fmt', 'fmt_dep'])
 | 
			
		||||
 | 
			
		||||
if compiler.get_id() == 'clang' and compiler.version().version_compare('<15')
 | 
			
		||||
  fmt_dep = declare_dependency(
 | 
			
		||||
    dependencies: fmt_dep,
 | 
			
		||||
    # suppress bogus clang 14 warning (the version in Android NDK r25b)
 | 
			
		||||
    compile_args: ['-Wno-unused-local-typedef'],
 | 
			
		||||
  )
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
log = static_library(
 | 
			
		||||
  'log',
 | 
			
		||||
  'src/Log.cxx',
 | 
			
		||||
@@ -349,7 +359,7 @@ sources = [
 | 
			
		||||
  'src/TagStream.cxx',
 | 
			
		||||
  'src/TagAny.cxx',
 | 
			
		||||
  'src/TimePrint.cxx',
 | 
			
		||||
  'src/mixer/Volume.cxx',
 | 
			
		||||
  'src/mixer/Memento.cxx',
 | 
			
		||||
  'src/PlaylistFile.cxx',
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
@@ -379,6 +389,7 @@ endif
 | 
			
		||||
 | 
			
		||||
if enable_database
 | 
			
		||||
  sources += [
 | 
			
		||||
    'src/storage/StorageState.cxx',
 | 
			
		||||
    'src/queue/PlaylistUpdate.cxx',
 | 
			
		||||
    'src/command/StorageCommands.cxx',
 | 
			
		||||
    'src/command/DatabaseCommands.cxx',
 | 
			
		||||
 
 | 
			
		||||
@@ -1,26 +1,32 @@
 | 
			
		||||
import os.path, subprocess, sys
 | 
			
		||||
from typing import Collection, Iterable, Optional, Sequence, Union
 | 
			
		||||
from collections.abc import Mapping
 | 
			
		||||
 | 
			
		||||
from build.makeproject import MakeProject
 | 
			
		||||
from .toolchain import AnyToolchain
 | 
			
		||||
 | 
			
		||||
class AutotoolsProject(MakeProject):
 | 
			
		||||
    def __init__(self, url, md5, installed, configure_args=[],
 | 
			
		||||
                 autogen=False,
 | 
			
		||||
                 autoreconf=False,
 | 
			
		||||
                 cppflags='',
 | 
			
		||||
                 ldflags='',
 | 
			
		||||
                 libs='',
 | 
			
		||||
                 subdirs=None,
 | 
			
		||||
    def __init__(self, url: Union[str, Sequence[str]], md5: str, installed: str,
 | 
			
		||||
                 configure_args: Iterable[str]=[],
 | 
			
		||||
                 autogen: bool=False,
 | 
			
		||||
                 autoreconf: bool=False,
 | 
			
		||||
                 per_arch_cflags: Optional[Mapping[str, str]]=None,
 | 
			
		||||
                 cppflags: str='',
 | 
			
		||||
                 ldflags: str='',
 | 
			
		||||
                 libs: str='',
 | 
			
		||||
                 subdirs: Optional[Collection[str]]=None,
 | 
			
		||||
                 **kwargs):
 | 
			
		||||
        MakeProject.__init__(self, url, md5, installed, **kwargs)
 | 
			
		||||
        self.configure_args = configure_args
 | 
			
		||||
        self.autogen = autogen
 | 
			
		||||
        self.autoreconf = autoreconf
 | 
			
		||||
        self.per_arch_cflags = per_arch_cflags
 | 
			
		||||
        self.cppflags = cppflags
 | 
			
		||||
        self.ldflags = ldflags
 | 
			
		||||
        self.libs = libs
 | 
			
		||||
        self.subdirs = subdirs
 | 
			
		||||
 | 
			
		||||
    def configure(self, toolchain):
 | 
			
		||||
    def configure(self, toolchain: AnyToolchain) -> str:
 | 
			
		||||
        src = self.unpack(toolchain)
 | 
			
		||||
        if self.autogen:
 | 
			
		||||
            if sys.platform == 'darwin':
 | 
			
		||||
@@ -35,27 +41,48 @@ class AutotoolsProject(MakeProject):
 | 
			
		||||
 | 
			
		||||
        build = self.make_build_path(toolchain)
 | 
			
		||||
 | 
			
		||||
        arch_cflags = ''
 | 
			
		||||
        if self.per_arch_cflags is not None and toolchain.host_triplet is not None:
 | 
			
		||||
            arch_cflags = self.per_arch_cflags.get(toolchain.host_triplet, '')
 | 
			
		||||
 | 
			
		||||
        configure = [
 | 
			
		||||
            os.path.join(src, 'configure'),
 | 
			
		||||
            'CC=' + toolchain.cc,
 | 
			
		||||
            'CXX=' + toolchain.cxx,
 | 
			
		||||
            'CFLAGS=' + toolchain.cflags,
 | 
			
		||||
            'CXXFLAGS=' + toolchain.cxxflags,
 | 
			
		||||
            'CFLAGS=' + toolchain.cflags + ' ' + arch_cflags,
 | 
			
		||||
            'CXXFLAGS=' + toolchain.cxxflags + ' ' + arch_cflags,
 | 
			
		||||
            'CPPFLAGS=' + toolchain.cppflags + ' ' + self.cppflags,
 | 
			
		||||
            'LDFLAGS=' + toolchain.ldflags + ' ' + self.ldflags,
 | 
			
		||||
            'LIBS=' + toolchain.libs + ' ' + self.libs,
 | 
			
		||||
            'AR=' + toolchain.ar,
 | 
			
		||||
            'ARFLAGS=' + toolchain.arflags,
 | 
			
		||||
            'RANLIB=' + toolchain.ranlib,
 | 
			
		||||
            'STRIP=' + toolchain.strip,
 | 
			
		||||
            '--host=' + toolchain.arch,
 | 
			
		||||
            '--prefix=' + toolchain.install_prefix,
 | 
			
		||||
            '--enable-silent-rules',
 | 
			
		||||
        ] + self.configure_args
 | 
			
		||||
            '--disable-silent-rules',
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        if toolchain.host_triplet is not None:
 | 
			
		||||
            configure.append('--host=' + toolchain.host_triplet)
 | 
			
		||||
 | 
			
		||||
        configure.extend(self.configure_args)
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            print(configure)
 | 
			
		||||
            subprocess.check_call(configure, cwd=build, env=toolchain.env)
 | 
			
		||||
        except subprocess.CalledProcessError:
 | 
			
		||||
            # dump config.log after a failed configure run
 | 
			
		||||
            try:
 | 
			
		||||
                with open(os.path.join(build, 'config.log')) as f:
 | 
			
		||||
                    sys.stdout.write(f.read())
 | 
			
		||||
            except:
 | 
			
		||||
                pass
 | 
			
		||||
            # re-raise the exception
 | 
			
		||||
            raise
 | 
			
		||||
 | 
			
		||||
        subprocess.check_call(configure, cwd=build, env=toolchain.env)
 | 
			
		||||
        return build
 | 
			
		||||
 | 
			
		||||
    def _build(self, toolchain):
 | 
			
		||||
    def _build(self, toolchain: AnyToolchain) -> None:
 | 
			
		||||
        build = self.configure(toolchain)
 | 
			
		||||
        if self.subdirs is not None:
 | 
			
		||||
            for subdir in self.subdirs:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,21 @@
 | 
			
		||||
import os
 | 
			
		||||
import re
 | 
			
		||||
import subprocess
 | 
			
		||||
from typing import cast, Optional, Sequence, TextIO, Union
 | 
			
		||||
from collections.abc import Mapping
 | 
			
		||||
 | 
			
		||||
from build.project import Project
 | 
			
		||||
from .toolchain import AnyToolchain
 | 
			
		||||
 | 
			
		||||
def __write_cmake_compiler(f, language, compiler):
 | 
			
		||||
def __write_cmake_compiler(f: TextIO, language: str, compiler: str) -> None:
 | 
			
		||||
    s = compiler.split(' ', 1)
 | 
			
		||||
    if len(s) == 2:
 | 
			
		||||
        print(f'set(CMAKE_{language}_COMPILER_LAUNCHER {s[0]})', file=f)
 | 
			
		||||
        compiler = s[1]
 | 
			
		||||
    print(f'set(CMAKE_{language}_COMPILER {compiler})', file=f)
 | 
			
		||||
 | 
			
		||||
def __write_cmake_toolchain_file(f, toolchain):
 | 
			
		||||
    if '-darwin' in toolchain.actual_arch:
 | 
			
		||||
def __write_cmake_toolchain_file(f: TextIO, toolchain: AnyToolchain) -> None:
 | 
			
		||||
    if toolchain.is_darwin:
 | 
			
		||||
        cmake_system_name = 'Darwin'
 | 
			
		||||
    elif toolchain.is_windows:
 | 
			
		||||
        cmake_system_name = 'Windows'
 | 
			
		||||
@@ -20,67 +24,107 @@ def __write_cmake_toolchain_file(f, toolchain):
 | 
			
		||||
 | 
			
		||||
    f.write(f"""
 | 
			
		||||
set(CMAKE_SYSTEM_NAME {cmake_system_name})
 | 
			
		||||
set(CMAKE_SYSTEM_PROCESSOR {toolchain.actual_arch.split('-', 1)[0]})
 | 
			
		||||
set(CMAKE_SYSTEM_PROCESSOR {toolchain.host_triplet.split('-', 1)[0]})
 | 
			
		||||
 | 
			
		||||
set(CMAKE_C_COMPILER_TARGET {toolchain.actual_arch})
 | 
			
		||||
set(CMAKE_CXX_COMPILER_TARGET {toolchain.actual_arch})
 | 
			
		||||
set(CMAKE_C_COMPILER_TARGET {toolchain.host_triplet})
 | 
			
		||||
set(CMAKE_CXX_COMPILER_TARGET {toolchain.host_triplet})
 | 
			
		||||
 | 
			
		||||
set(CMAKE_C_FLAGS "{toolchain.cflags} {toolchain.cppflags}")
 | 
			
		||||
set(CMAKE_CXX_FLAGS "{toolchain.cxxflags} {toolchain.cppflags}")
 | 
			
		||||
set(CMAKE_C_FLAGS_INIT "{toolchain.cflags} {toolchain.cppflags}")
 | 
			
		||||
set(CMAKE_CXX_FLAGS_INIT "{toolchain.cxxflags} {toolchain.cppflags}")
 | 
			
		||||
""")
 | 
			
		||||
    __write_cmake_compiler(f, 'C', toolchain.cc)
 | 
			
		||||
    __write_cmake_compiler(f, 'CXX', toolchain.cxx)
 | 
			
		||||
 | 
			
		||||
def configure(toolchain, src, build, args=()):
 | 
			
		||||
    cross_args = []
 | 
			
		||||
    if cmake_system_name == 'Darwin':
 | 
			
		||||
        # On macOS, cmake forcibly adds an "-isysroot" flag even if
 | 
			
		||||
        # one is already present in the flags variable; this breaks
 | 
			
		||||
        # cross-compiling for iOS, and can be worked around by setting
 | 
			
		||||
        # the CMAKE_OSX_SYSROOT variable
 | 
			
		||||
        # (https://cmake.org/cmake/help/latest/variable/CMAKE_OSX_SYSROOT.html).
 | 
			
		||||
        m = re.search(r'-isysroot +(\S+)', toolchain.cflags)
 | 
			
		||||
        if m:
 | 
			
		||||
            sysroot = m.group(1)
 | 
			
		||||
 | 
			
		||||
            print(f'set(CMAKE_OSX_SYSROOT {sysroot})', file=f)
 | 
			
		||||
 | 
			
		||||
            # search libraries and headers only in the sysroot, not on
 | 
			
		||||
            # the build host
 | 
			
		||||
            f.write(f"""
 | 
			
		||||
set(CMAKE_FIND_ROOT_PATH "{toolchain.install_prefix};{sysroot}")
 | 
			
		||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
 | 
			
		||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
 | 
			
		||||
""")
 | 
			
		||||
    elif cmake_system_name == 'Windows':
 | 
			
		||||
            # search libraries and headers only in the sysroot, not on
 | 
			
		||||
            # the build host
 | 
			
		||||
            f.write(f"""
 | 
			
		||||
set(CMAKE_FIND_ROOT_PATH "{toolchain.install_prefix}")
 | 
			
		||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
 | 
			
		||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
 | 
			
		||||
""")
 | 
			
		||||
 | 
			
		||||
def configure(toolchain: AnyToolchain, src: str, build: str, args: list[str]=[], env: Optional[Mapping[str, str]]=None) -> None:
 | 
			
		||||
    cross_args: list[str] = []
 | 
			
		||||
 | 
			
		||||
    if toolchain.is_windows:
 | 
			
		||||
        cross_args.append('-DCMAKE_RC_COMPILER=' + toolchain.windres)
 | 
			
		||||
 | 
			
		||||
    # Several targets need a sysroot to prevent pkg-config from
 | 
			
		||||
    # looking for libraries on the build host (TODO: fix this
 | 
			
		||||
    # properly); but we must not do that on Android because the NDK
 | 
			
		||||
    # has a sysroot already
 | 
			
		||||
    if '-android' not in toolchain.actual_arch and '-darwin' not in toolchain.actual_arch:
 | 
			
		||||
        cross_args.append('-DCMAKE_SYSROOT=' + toolchain.install_prefix)
 | 
			
		||||
 | 
			
		||||
    os.makedirs(build, exist_ok=True)
 | 
			
		||||
    cmake_toolchain_file = os.path.join(build, 'cmake_toolchain_file')
 | 
			
		||||
    with open(cmake_toolchain_file, 'w') as f:
 | 
			
		||||
        __write_cmake_toolchain_file(f, toolchain)
 | 
			
		||||
        cross_args.append('-DCMAKE_RC_COMPILER=' + cast(str, toolchain.windres))
 | 
			
		||||
 | 
			
		||||
    configure = [
 | 
			
		||||
        'cmake',
 | 
			
		||||
        src,
 | 
			
		||||
 | 
			
		||||
        '-DCMAKE_TOOLCHAIN_FILE=' + cmake_toolchain_file,
 | 
			
		||||
 | 
			
		||||
        '-DCMAKE_INSTALL_PREFIX=' + toolchain.install_prefix,
 | 
			
		||||
        '-DCMAKE_BUILD_TYPE=release',
 | 
			
		||||
 | 
			
		||||
        '-GNinja',
 | 
			
		||||
    ] + cross_args + args
 | 
			
		||||
 | 
			
		||||
    subprocess.check_call(configure, env=toolchain.env, cwd=build)
 | 
			
		||||
    if toolchain.host_triplet is not None:
 | 
			
		||||
        # cross-compiling: write a toolchain file
 | 
			
		||||
        os.makedirs(build, exist_ok=True)
 | 
			
		||||
 | 
			
		||||
        # Several targets need a sysroot to prevent pkg-config from
 | 
			
		||||
        # looking for libraries on the build host (TODO: fix this
 | 
			
		||||
        # properly); but we must not do that on Android because the NDK
 | 
			
		||||
        # has a sysroot already
 | 
			
		||||
        if not toolchain.is_android and not toolchain.is_darwin:
 | 
			
		||||
            cross_args.append('-DCMAKE_SYSROOT=' + toolchain.install_prefix)
 | 
			
		||||
 | 
			
		||||
        cmake_toolchain_file = os.path.join(build, 'cmake_toolchain_file')
 | 
			
		||||
        with open(cmake_toolchain_file, 'w') as f:
 | 
			
		||||
            __write_cmake_toolchain_file(f, toolchain)
 | 
			
		||||
 | 
			
		||||
        configure.append('-DCMAKE_TOOLCHAIN_FILE=' + cmake_toolchain_file)
 | 
			
		||||
 | 
			
		||||
    if env is None:
 | 
			
		||||
        env = toolchain.env
 | 
			
		||||
    else:
 | 
			
		||||
        env = {**toolchain.env, **env}
 | 
			
		||||
 | 
			
		||||
    print(configure)
 | 
			
		||||
    subprocess.check_call(configure, env=env, cwd=build)
 | 
			
		||||
 | 
			
		||||
class CmakeProject(Project):
 | 
			
		||||
    def __init__(self, url, md5, installed, configure_args=[],
 | 
			
		||||
                 windows_configure_args=[],
 | 
			
		||||
    def __init__(self, url: Union[str, Sequence[str]], md5: str, installed: str,
 | 
			
		||||
                 configure_args: list[str]=[],
 | 
			
		||||
                 windows_configure_args: list[str]=[],
 | 
			
		||||
                 env: Optional[Mapping[str, str]]=None,
 | 
			
		||||
                 **kwargs):
 | 
			
		||||
        Project.__init__(self, url, md5, installed, **kwargs)
 | 
			
		||||
        self.configure_args = configure_args
 | 
			
		||||
        self.windows_configure_args = windows_configure_args
 | 
			
		||||
        self.env = env
 | 
			
		||||
 | 
			
		||||
    def configure(self, toolchain):
 | 
			
		||||
    def configure(self, toolchain: AnyToolchain) -> str:
 | 
			
		||||
        src = self.unpack(toolchain)
 | 
			
		||||
        build = self.make_build_path(toolchain)
 | 
			
		||||
        configure_args = self.configure_args
 | 
			
		||||
        if toolchain.is_windows:
 | 
			
		||||
            configure_args = configure_args + self.windows_configure_args
 | 
			
		||||
        configure(toolchain, src, build, configure_args)
 | 
			
		||||
        configure(toolchain, src, build, configure_args, self.env)
 | 
			
		||||
        return build
 | 
			
		||||
 | 
			
		||||
    def _build(self, toolchain):
 | 
			
		||||
    def _build(self, toolchain: AnyToolchain) -> None:
 | 
			
		||||
        build = self.configure(toolchain)
 | 
			
		||||
        subprocess.check_call(['ninja', 'install'],
 | 
			
		||||
        subprocess.check_call(['ninja', '-v', 'install'],
 | 
			
		||||
                              cwd=build, env=toolchain.env)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,50 @@
 | 
			
		||||
from build.verify import verify_file_digest
 | 
			
		||||
from typing import Sequence, Union
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
import urllib.request
 | 
			
		||||
 | 
			
		||||
def download_and_verify(url, md5, parent_path):
 | 
			
		||||
from .verify import verify_file_digest
 | 
			
		||||
 | 
			
		||||
def __to_string_sequence(x: Union[str, Sequence[str]]) -> Sequence[str]:
 | 
			
		||||
    if isinstance(x, str):
 | 
			
		||||
        return (x,)
 | 
			
		||||
    else:
 | 
			
		||||
        return x
 | 
			
		||||
 | 
			
		||||
def __get_any(x: Union[str, Sequence[str]]) -> str:
 | 
			
		||||
    if isinstance(x, str):
 | 
			
		||||
        return x
 | 
			
		||||
    else:
 | 
			
		||||
        return x[0]
 | 
			
		||||
 | 
			
		||||
def __download_one(url: str, path: str) -> None:
 | 
			
		||||
    print("download", url)
 | 
			
		||||
    urllib.request.urlretrieve(url, path)
 | 
			
		||||
 | 
			
		||||
def __download(urls: Sequence[str], path: str) -> None:
 | 
			
		||||
    for url in urls[:-1]:
 | 
			
		||||
        try:
 | 
			
		||||
            __download_one(url, path)
 | 
			
		||||
            return
 | 
			
		||||
        except:
 | 
			
		||||
            print("download error:", sys.exc_info()[0])
 | 
			
		||||
    __download_one(urls[-1], path)
 | 
			
		||||
 | 
			
		||||
def __download_and_verify_to(urls: Sequence[str], md5: str, path: str) -> None:
 | 
			
		||||
    __download(urls, path)
 | 
			
		||||
    if not verify_file_digest(path, md5):
 | 
			
		||||
        raise RuntimeError("Digest mismatch")
 | 
			
		||||
 | 
			
		||||
def download_basename(urls: Union[str, Sequence[str]]) -> str:
 | 
			
		||||
    return os.path.basename(__get_any(urls))
 | 
			
		||||
 | 
			
		||||
def download_and_verify(urls: Union[str, Sequence[str]], md5: str, parent_path: str) -> str:
 | 
			
		||||
    """Download a file, verify its MD5 checksum and return the local path."""
 | 
			
		||||
 | 
			
		||||
    base = download_basename(urls)
 | 
			
		||||
 | 
			
		||||
    os.makedirs(parent_path, exist_ok=True)
 | 
			
		||||
    path = os.path.join(parent_path, os.path.basename(url))
 | 
			
		||||
    path = os.path.join(parent_path, base)
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        if verify_file_digest(path, md5): return path
 | 
			
		||||
@@ -16,11 +54,6 @@ def download_and_verify(url, md5, parent_path):
 | 
			
		||||
 | 
			
		||||
    tmp_path = path + '.tmp'
 | 
			
		||||
 | 
			
		||||
    print("download", url)
 | 
			
		||||
    urllib.request.urlretrieve(url, tmp_path)
 | 
			
		||||
    if not verify_file_digest(tmp_path, md5):
 | 
			
		||||
        os.unlink(tmp_path)
 | 
			
		||||
        raise RuntimeError("Digest mismatch")
 | 
			
		||||
 | 
			
		||||
    __download_and_verify_to(__to_string_sequence(urls), md5, tmp_path)
 | 
			
		||||
    os.rename(tmp_path, path)
 | 
			
		||||
    return path
 | 
			
		||||
 
 | 
			
		||||
@@ -29,8 +29,8 @@ libogg = CmakeProject(
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
opus = AutotoolsProject(
 | 
			
		||||
    'https://archive.mozilla.org/pub/opus/opus-1.3.1.tar.gz',
 | 
			
		||||
    '65b58e1e25b2a114157014736a3d9dfeaad8d41be1c8179866f144a2fb44ff9d',
 | 
			
		||||
    'https://downloads.xiph.org/releases/opus/opus-1.4.tar.gz',
 | 
			
		||||
    'c9b32b4253be5ae63d1ff16eea06b94b5f0f2951b7a02aceef58e3a3ce49c51f',
 | 
			
		||||
    'lib/libopus.a',
 | 
			
		||||
    [
 | 
			
		||||
        '--disable-shared', '--enable-static',
 | 
			
		||||
@@ -43,20 +43,23 @@ opus = AutotoolsProject(
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
flac = AutotoolsProject(
 | 
			
		||||
    'http://downloads.xiph.org/releases/flac/flac-1.3.4.tar.xz',
 | 
			
		||||
    '8ff0607e75a322dd7cd6ec48f4f225471404ae2730d0ea945127b1355155e737',
 | 
			
		||||
    'http://downloads.xiph.org/releases/flac/flac-1.4.3.tar.xz',
 | 
			
		||||
    '6c58e69cd22348f441b861092b825e591d0b822e106de6eb0ee4d05d27205b70',
 | 
			
		||||
    'lib/libFLAC.a',
 | 
			
		||||
    [
 | 
			
		||||
        '--disable-shared', '--enable-static',
 | 
			
		||||
        '--disable-stack-smash-protection',
 | 
			
		||||
        '--disable-xmms-plugin', '--disable-cpplibs',
 | 
			
		||||
        '--disable-doxygen-docs',
 | 
			
		||||
        '--disable-programs',
 | 
			
		||||
    ],
 | 
			
		||||
    subdirs=['include', 'src/libFLAC'],
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
zlib = ZlibProject(
 | 
			
		||||
    'http://zlib.net/zlib-1.2.11.tar.xz',
 | 
			
		||||
    '4ff941449631ace0d4d203e3483be9dbc9da454084111f97ea0a2114e19bf066',
 | 
			
		||||
    ('http://zlib.net/zlib-1.3.tar.xz',
 | 
			
		||||
     'https://github.com/madler/zlib/releases/download/v1.3/zlib-1.3.tar.xz'),
 | 
			
		||||
    '8a9ba2898e1d0d774eca6ba5b4627a11e5588ba85c8851336eb38de4683050a7',
 | 
			
		||||
    'lib/libz.a',
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -109,33 +112,35 @@ libmodplug = AutotoolsProject(
 | 
			
		||||
    [
 | 
			
		||||
        '--disable-shared', '--enable-static',
 | 
			
		||||
    ],
 | 
			
		||||
    patches='src/lib/modplug/patches',
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
libopenmpt = AutotoolsProject(
 | 
			
		||||
    'https://lib.openmpt.org/files/libopenmpt/src/libopenmpt-0.5.12+release.autotools.tar.gz',
 | 
			
		||||
    '892aea7a599b5d21842bebf463b5aafdad5711be7008dd84401920c6234820af',
 | 
			
		||||
    'https://lib.openmpt.org/files/libopenmpt/src/libopenmpt-0.7.3+release.autotools.tar.gz',
 | 
			
		||||
    '2cf8369b7916b09264f3f14b9fb6cef35a6e9bee0328dec4f49d98211ccfd722',
 | 
			
		||||
    'lib/libopenmpt.a',
 | 
			
		||||
    [
 | 
			
		||||
        '--disable-shared', '--enable-static',
 | 
			
		||||
        '--disable-openmpt123',
 | 
			
		||||
        '--disable-examples',
 | 
			
		||||
        '--disable-tests',
 | 
			
		||||
        '--disable-doxygen-doc',
 | 
			
		||||
        '--without-mpg123', '--without-ogg', '--without-vorbis', '--without-vorbisfile',
 | 
			
		||||
        '--without-portaudio', '--without-portaudiocpp', '--without-sndfile',
 | 
			
		||||
        '--without-flac',
 | 
			
		||||
    ],
 | 
			
		||||
    base='libopenmpt-0.5.12+release.autotools',
 | 
			
		||||
    base='libopenmpt-0.6.6+release.autotools',
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
wildmidi = CmakeProject(
 | 
			
		||||
    'https://codeload.github.com/Mindwerks/wildmidi/tar.gz/wildmidi-0.4.4',
 | 
			
		||||
    '6f267c8d331e9859906837e2c197093fddec31829d2ebf7b958cf6b7ae935430',
 | 
			
		||||
    'https://github.com/Mindwerks/wildmidi/releases/download/wildmidi-0.4.5/wildmidi-0.4.5.tar.gz',
 | 
			
		||||
    'd5e7bef00a7aa47534a53d43b1265f8d3d27f6a28e7f563c1cdf02ff4fa35b99',
 | 
			
		||||
    'lib/libWildMidi.a',
 | 
			
		||||
    [
 | 
			
		||||
        '-DBUILD_SHARED_LIBS=OFF',
 | 
			
		||||
        '-DWANT_PLAYER=OFF',
 | 
			
		||||
        '-DWANT_STATIC=ON',
 | 
			
		||||
    ],
 | 
			
		||||
    base='wildmidi-wildmidi-0.4.4',
 | 
			
		||||
    name='wildmidi',
 | 
			
		||||
    version='0.4.4',
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
gme = CmakeProject(
 | 
			
		||||
@@ -146,13 +151,13 @@ gme = CmakeProject(
 | 
			
		||||
        '-DBUILD_SHARED_LIBS=OFF',
 | 
			
		||||
        '-DENABLE_UBSAN=OFF',
 | 
			
		||||
        '-DZLIB_INCLUDE_DIR=OFF',
 | 
			
		||||
        '-DSDL2_DIR=OFF',
 | 
			
		||||
        '-DCMAKE_DISABLE_FIND_PACKAGE_SDL2=ON',
 | 
			
		||||
    ],
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
ffmpeg = FfmpegProject(
 | 
			
		||||
    'http://ffmpeg.org/releases/ffmpeg-5.0.tar.xz',
 | 
			
		||||
    '51e919f7d205062c0fd4fae6243a84850391115104ccf1efc451733bc0ac7298',
 | 
			
		||||
    'http://ffmpeg.org/releases/ffmpeg-6.1.tar.xz',
 | 
			
		||||
    '488c76e57dd9b3bee901f71d5c95eaf1db4a5a31fe46a28654e837144207c270',
 | 
			
		||||
    'lib/libavcodec.a',
 | 
			
		||||
    [
 | 
			
		||||
        '--disable-shared', '--enable-static',
 | 
			
		||||
@@ -166,17 +171,21 @@ ffmpeg = FfmpegProject(
 | 
			
		||||
        '--disable-swscale',
 | 
			
		||||
        '--disable-postproc',
 | 
			
		||||
        '--disable-avfilter',
 | 
			
		||||
        '--disable-lzo',
 | 
			
		||||
        '--disable-faan',
 | 
			
		||||
        '--disable-pixelutils',
 | 
			
		||||
        '--disable-network',
 | 
			
		||||
        '--disable-encoders',
 | 
			
		||||
        '--disable-hwaccels',
 | 
			
		||||
        '--disable-muxers',
 | 
			
		||||
        '--disable-protocols',
 | 
			
		||||
        '--disable-devices',
 | 
			
		||||
        '--disable-filters',
 | 
			
		||||
        '--disable-v4l2_m2m',
 | 
			
		||||
 | 
			
		||||
        '--disable-sdl2',
 | 
			
		||||
        '--disable-vulkan',
 | 
			
		||||
        '--disable-xlib',
 | 
			
		||||
 | 
			
		||||
        '--disable-parser=bmp',
 | 
			
		||||
        '--disable-parser=cavsvideo',
 | 
			
		||||
        '--disable-parser=dvbsub',
 | 
			
		||||
@@ -189,17 +198,22 @@ ffmpeg = FfmpegProject(
 | 
			
		||||
        '--disable-parser=h263',
 | 
			
		||||
        '--disable-parser=h264',
 | 
			
		||||
        '--disable-parser=hevc',
 | 
			
		||||
        '--disable-parser=jpeg2000',
 | 
			
		||||
        '--disable-parser=mjpeg',
 | 
			
		||||
        '--disable-parser=mlp',
 | 
			
		||||
        '--disable-parser=mpeg4video',
 | 
			
		||||
        '--disable-parser=mpegvideo',
 | 
			
		||||
        '--disable-parser=opus',
 | 
			
		||||
        '--disable-parser=qoi',
 | 
			
		||||
        '--disable-parser=rv30',
 | 
			
		||||
        '--disable-parser=rv40',
 | 
			
		||||
        '--disable-parser=vc1',
 | 
			
		||||
        '--disable-parser=vp3',
 | 
			
		||||
        '--disable-parser=vp8',
 | 
			
		||||
        '--disable-parser=vp9',
 | 
			
		||||
        '--disable-parser=png',
 | 
			
		||||
        '--disable-parser=pnm',
 | 
			
		||||
        '--disable-parser=webp',
 | 
			
		||||
        '--disable-parser=xma',
 | 
			
		||||
 | 
			
		||||
        '--disable-demuxer=aqtitle',
 | 
			
		||||
@@ -215,6 +229,42 @@ ffmpeg = FfmpegProject(
 | 
			
		||||
        '--disable-demuxer=h264',
 | 
			
		||||
        '--disable-demuxer=ico',
 | 
			
		||||
        '--disable-demuxer=image2',
 | 
			
		||||
        '--disable-demuxer=image2pipe',
 | 
			
		||||
        '--disable-demuxer=image_bmp_pipe',
 | 
			
		||||
        '--disable-demuxer=image_cri_pipe',
 | 
			
		||||
        '--disable-demuxer=image_dds_pipe',
 | 
			
		||||
        '--disable-demuxer=image_dpx_pipe',
 | 
			
		||||
        '--disable-demuxer=image_exr_pipe',
 | 
			
		||||
        '--disable-demuxer=image_gem_pipe',
 | 
			
		||||
        '--disable-demuxer=image_gif_pipe',
 | 
			
		||||
        '--disable-demuxer=image_j2k_pipe',
 | 
			
		||||
        '--disable-demuxer=image_jpeg_pipe',
 | 
			
		||||
        '--disable-demuxer=image_jpegls_pipe',
 | 
			
		||||
        '--disable-demuxer=image_jpegxl_pipe',
 | 
			
		||||
        '--disable-demuxer=image_pam_pipe',
 | 
			
		||||
        '--disable-demuxer=image_pbm_pipe',
 | 
			
		||||
        '--disable-demuxer=image_pcx_pipe',
 | 
			
		||||
        '--disable-demuxer=image_pfm_pipe',
 | 
			
		||||
        '--disable-demuxer=image_pgm_pipe',
 | 
			
		||||
        '--disable-demuxer=image_pgmyuv_pipe',
 | 
			
		||||
        '--disable-demuxer=image_pgx_pipe',
 | 
			
		||||
        '--disable-demuxer=image_phm_pipe',
 | 
			
		||||
        '--disable-demuxer=image_photocd_pipe',
 | 
			
		||||
        '--disable-demuxer=image_pictor_pipe',
 | 
			
		||||
        '--disable-demuxer=image_png_pipe',
 | 
			
		||||
        '--disable-demuxer=image_ppm_pipe',
 | 
			
		||||
        '--disable-demuxer=image_psd_pipe',
 | 
			
		||||
        '--disable-demuxer=image_qdraw_pipe',
 | 
			
		||||
        '--disable-demuxer=image_qoi_pipe',
 | 
			
		||||
        '--disable-demuxer=image_sgi_pipe',
 | 
			
		||||
        '--disable-demuxer=image_sunrast_pipe',
 | 
			
		||||
        '--disable-demuxer=image_svg_pipe',
 | 
			
		||||
        '--disable-demuxer=image_tiff_pipe',
 | 
			
		||||
        '--disable-demuxer=image_vbn_pipe',
 | 
			
		||||
        '--disable-demuxer=image_webp_pipe',
 | 
			
		||||
        '--disable-demuxer=image_xbm_pipe',
 | 
			
		||||
        '--disable-demuxer=image_xpm_pipe',
 | 
			
		||||
        '--disable-demuxer=image_xwd_pipe',
 | 
			
		||||
        '--disable-demuxer=jacosub',
 | 
			
		||||
        '--disable-demuxer=lrc',
 | 
			
		||||
        '--disable-demuxer=microdvd',
 | 
			
		||||
@@ -237,6 +287,7 @@ ffmpeg = FfmpegProject(
 | 
			
		||||
        '--disable-demuxer=tedcaptions',
 | 
			
		||||
        '--disable-demuxer=vobsub',
 | 
			
		||||
        '--disable-demuxer=vplayer',
 | 
			
		||||
        '--disable-demuxer=webm_dash_manifest',
 | 
			
		||||
        '--disable-demuxer=webvtt',
 | 
			
		||||
        '--disable-demuxer=yuv4mpegpipe',
 | 
			
		||||
 | 
			
		||||
@@ -266,78 +317,181 @@ ffmpeg = FfmpegProject(
 | 
			
		||||
        '--disable-decoder=qdmc',
 | 
			
		||||
 | 
			
		||||
        # disable lots of image and video codecs
 | 
			
		||||
        '--disable-decoder=acelp_kelvin',
 | 
			
		||||
        '--disable-decoder=agm',
 | 
			
		||||
        '--disable-decoder=aic',
 | 
			
		||||
        '--disable-decoder=alias_pix',
 | 
			
		||||
        '--disable-decoder=ansi',
 | 
			
		||||
        '--disable-decoder=apng',
 | 
			
		||||
        '--disable-decoder=arbc',
 | 
			
		||||
        '--disable-decoder=argo',
 | 
			
		||||
        '--disable-decoder=ass',
 | 
			
		||||
        '--disable-decoder=asv1',
 | 
			
		||||
        '--disable-decoder=asv2',
 | 
			
		||||
        '--disable-decoder=apng',
 | 
			
		||||
        '--disable-decoder=aura',
 | 
			
		||||
        '--disable-decoder=aura2',
 | 
			
		||||
        '--disable-decoder=avrn',
 | 
			
		||||
        '--disable-decoder=avrp',
 | 
			
		||||
        '--disable-decoder=avui',
 | 
			
		||||
        '--disable-decoder=ayuv',
 | 
			
		||||
        '--disable-decoder=bethsoftvid',
 | 
			
		||||
        '--disable-decoder=bfi',
 | 
			
		||||
        '--disable-decoder=bink',
 | 
			
		||||
        '--disable-decoder=bintext',
 | 
			
		||||
        '--disable-decoder=bitpacked',
 | 
			
		||||
        '--disable-decoder=bmp',
 | 
			
		||||
        '--disable-decoder=bmv_video',
 | 
			
		||||
        '--disable-decoder=brender_pix',
 | 
			
		||||
        '--disable-decoder=c93',
 | 
			
		||||
        '--disable-decoder=cavs',
 | 
			
		||||
        '--disable-decoder=ccaption',
 | 
			
		||||
        '--disable-decoder=cdgraphics',
 | 
			
		||||
        '--disable-decoder=cdtoons',
 | 
			
		||||
        '--disable-decoder=cdxl',
 | 
			
		||||
        '--disable-decoder=cfhd',
 | 
			
		||||
        '--disable-decoder=cinepak',
 | 
			
		||||
        '--disable-decoder=clearvideo',
 | 
			
		||||
        '--disable-decoder=cljr',
 | 
			
		||||
        '--disable-decoder=cllc',
 | 
			
		||||
        '--disable-decoder=cpia',
 | 
			
		||||
        '--disable-decoder=cscd',
 | 
			
		||||
        '--disable-decoder=cyuv',
 | 
			
		||||
        '--disable-decoder=dds',
 | 
			
		||||
        '--disable-decoder=dirac',
 | 
			
		||||
        '--disable-decoder=dnxhd',
 | 
			
		||||
        '--disable-decoder=dpx',
 | 
			
		||||
        '--disable-decoder=dsicinvideo',
 | 
			
		||||
        '--disable-decoder=dvbsub',
 | 
			
		||||
        '--disable-decoder=dvdsub',
 | 
			
		||||
        '--disable-decoder=dvvideo',
 | 
			
		||||
        '--disable-decoder=dxa',
 | 
			
		||||
        '--disable-decoder=dxtory',
 | 
			
		||||
        '--disable-decoder=dxv',
 | 
			
		||||
        '--disable-decoder=eacmv',
 | 
			
		||||
        '--disable-decoder=eamad',
 | 
			
		||||
        '--disable-decoder=eatgq',
 | 
			
		||||
        '--disable-decoder=eatgv',
 | 
			
		||||
        '--disable-decoder=eatqi',
 | 
			
		||||
        '--disable-decoder=eightbps',
 | 
			
		||||
        '--disable-decoder=escape124',
 | 
			
		||||
        '--disable-decoder=escape130',
 | 
			
		||||
        '--disable-decoder=exr',
 | 
			
		||||
        '--disable-decoder=ffv1',
 | 
			
		||||
        '--disable-decoder=ffvhuff',
 | 
			
		||||
        '--disable-decoder=ffwavesynth',
 | 
			
		||||
        '--disable-decoder=fic',
 | 
			
		||||
        '--disable-decoder=fits',
 | 
			
		||||
        '--disable-decoder=flashsv',
 | 
			
		||||
        '--disable-decoder=flashsv2',
 | 
			
		||||
        '--disable-decoder=flic',
 | 
			
		||||
        '--disable-decoder=flv',
 | 
			
		||||
        '--disable-decoder=fmvc',
 | 
			
		||||
        '--disable-decoder=fraps',
 | 
			
		||||
        '--disable-decoder=fourxm',
 | 
			
		||||
        '--disable-decoder=frwu',
 | 
			
		||||
        '--disable-decoder=g2m',
 | 
			
		||||
        '--disable-decoder=gdv',
 | 
			
		||||
        '--disable-decoder=gem',
 | 
			
		||||
        '--disable-decoder=gif',
 | 
			
		||||
        '--disable-decoder=h261',
 | 
			
		||||
        '--disable-decoder=h263',
 | 
			
		||||
        '--disable-decoder=h263i',
 | 
			
		||||
        '--disable-decoder=h263p',
 | 
			
		||||
        '--disable-decoder=h264',
 | 
			
		||||
        '--disable-decoder=hap',
 | 
			
		||||
        '--disable-decoder=hevc',
 | 
			
		||||
        '--disable-decoder=hnm4_video',
 | 
			
		||||
        '--disable-decoder=hq_hqa',
 | 
			
		||||
        '--disable-decoder=hqx',
 | 
			
		||||
        '--disable-decoder=huffyuv',
 | 
			
		||||
        '--disable-decoder=hymt',
 | 
			
		||||
        '--disable-decoder=idcin',
 | 
			
		||||
        '--disable-decoder=idf',
 | 
			
		||||
        '--disable-decoder=iff_ilbm',
 | 
			
		||||
        '--disable-decoder=imm4',
 | 
			
		||||
        '--disable-decoder=indeo2',
 | 
			
		||||
        '--disable-decoder=indeo3',
 | 
			
		||||
        '--disable-decoder=indeo4',
 | 
			
		||||
        '--disable-decoder=indeo5',
 | 
			
		||||
        '--disable-decoder=interplay_video',
 | 
			
		||||
        '--disable-decoder=ipu',
 | 
			
		||||
        '--disable-decoder=jacosub',
 | 
			
		||||
        '--disable-decoder=jpeg2000',
 | 
			
		||||
        '--disable-decoder=jpegls',
 | 
			
		||||
        '--disable-decoder=jv',
 | 
			
		||||
        '--disable-decoder=kgv1',
 | 
			
		||||
        '--disable-decoder=kmvc',
 | 
			
		||||
        '--disable-decoder=lagarith',
 | 
			
		||||
        '--disable-decoder=loco',
 | 
			
		||||
        '--disable-decoder=lscr',
 | 
			
		||||
        '--disable-decoder=m101',
 | 
			
		||||
        '--disable-decoder=magicyuv',
 | 
			
		||||
        '--disable-decoder=mdec',
 | 
			
		||||
        '--disable-decoder=microdvd',
 | 
			
		||||
        '--disable-decoder=mimic',
 | 
			
		||||
        '--disable-decoder=mjpeg',
 | 
			
		||||
        '--disable-decoder=mmvideo',
 | 
			
		||||
        '--disable-decoder=mpl2',
 | 
			
		||||
        '--disable-decoder=mobiclip',
 | 
			
		||||
        '--disable-decoder=motionpixels',
 | 
			
		||||
        '--disable-decoder=movtext',
 | 
			
		||||
        '--disable-decoder=mpeg1video',
 | 
			
		||||
        '--disable-decoder=mpeg2video',
 | 
			
		||||
        '--disable-decoder=mpeg4',
 | 
			
		||||
        '--disable-decoder=mpegvideo',
 | 
			
		||||
        '--disable-decoder=msa1',
 | 
			
		||||
        '--disable-decoder=mscc',
 | 
			
		||||
        '--disable-decoder=msmpeg4_crystalhd',
 | 
			
		||||
        '--disable-decoder=msmpeg4v1',
 | 
			
		||||
        '--disable-decoder=msmpeg4v2',
 | 
			
		||||
        '--disable-decoder=msmpeg4v3',
 | 
			
		||||
        '--disable-decoder=msp2',
 | 
			
		||||
        '--disable-decoder=msrle',
 | 
			
		||||
        '--disable-decoder=mss1',
 | 
			
		||||
        '--disable-decoder=msvideo1',
 | 
			
		||||
        '--disable-decoder=mszh',
 | 
			
		||||
        '--disable-decoder=mts2',
 | 
			
		||||
        '--disable-decoder=mv30',
 | 
			
		||||
        '--disable-decoder=mvc1',
 | 
			
		||||
        '--disable-decoder=mvc2',
 | 
			
		||||
        '--disable-decoder=mvdv',
 | 
			
		||||
        '--disable-decoder=mvha',
 | 
			
		||||
        '--disable-decoder=mwsc',
 | 
			
		||||
        '--disable-decoder=notchlc',
 | 
			
		||||
        '--disable-decoder=nuv',
 | 
			
		||||
        '--disable-decoder=on2avc',
 | 
			
		||||
        '--disable-decoder=paf_video',
 | 
			
		||||
        '--disable-decoder=pam',
 | 
			
		||||
        '--disable-decoder=pbm',
 | 
			
		||||
        '--disable-decoder=pcx',
 | 
			
		||||
        '--disable-decoder=pdv',
 | 
			
		||||
        '--disable-decoder=pfm',
 | 
			
		||||
        '--disable-decoder=pgm',
 | 
			
		||||
        '--disable-decoder=pgmyuv',
 | 
			
		||||
        '--disable-decoder=pgssub',
 | 
			
		||||
        '--disable-decoder=pgx',
 | 
			
		||||
        '--disable-decoder=phm',
 | 
			
		||||
        '--disable-decoder=photocd',
 | 
			
		||||
        '--disable-decoder=png',
 | 
			
		||||
        '--disable-decoder=pictor',
 | 
			
		||||
        '--disable-decoder=pixlet',
 | 
			
		||||
        '--disable-decoder=pjs',
 | 
			
		||||
        '--disable-decoder=ppm',
 | 
			
		||||
        '--disable-decoder=prores',
 | 
			
		||||
        '--disable-decoder=prosumer',
 | 
			
		||||
        '--disable-decoder=psd',
 | 
			
		||||
        '--disable-decoder=ptx',
 | 
			
		||||
        '--disable-decoder=qdraw',
 | 
			
		||||
        '--disable-decoder=qoi',
 | 
			
		||||
        '--disable-decoder=qpeg',
 | 
			
		||||
        '--disable-decoder=qtrle',
 | 
			
		||||
        '--disable-decoder=rawvideo',
 | 
			
		||||
        '--disable-decoder=r10k',
 | 
			
		||||
        '--disable-decoder=r210',
 | 
			
		||||
        '--disable-decoder=rasc',
 | 
			
		||||
        '--disable-decoder=realtext',
 | 
			
		||||
        '--disable-decoder=rl2',
 | 
			
		||||
        '--disable-decoder=rpza',
 | 
			
		||||
        '--disable-decoder=roq',
 | 
			
		||||
        '--disable-decoder=roq_dpcm',
 | 
			
		||||
        '--disable-decoder=rscc',
 | 
			
		||||
@@ -346,53 +500,122 @@ ffmpeg = FfmpegProject(
 | 
			
		||||
        '--disable-decoder=rv30',
 | 
			
		||||
        '--disable-decoder=rv40',
 | 
			
		||||
        '--disable-decoder=sami',
 | 
			
		||||
        '--disable-decoder=sanm',
 | 
			
		||||
        '--disable-decoder=scpr',
 | 
			
		||||
        '--disable-decoder=screenpresso',
 | 
			
		||||
        '--disable-decoder=sga',
 | 
			
		||||
        '--disable-decoder=sgi',
 | 
			
		||||
        '--disable-decoder=sgirle',
 | 
			
		||||
        '--disable-decoder=sheervideo',
 | 
			
		||||
        '--disable-decoder=simbiosis_imx',
 | 
			
		||||
        '--disable-decoder=smc',
 | 
			
		||||
        '--disable-decoder=snow',
 | 
			
		||||
        '--disable-decoder=speedhq',
 | 
			
		||||
        '--disable-decoder=srgc',
 | 
			
		||||
        '--disable-decoder=srt',
 | 
			
		||||
        '--disable-decoder=ssa',
 | 
			
		||||
        '--disable-decoder=stl',
 | 
			
		||||
        '--disable-decoder=subrip',
 | 
			
		||||
        '--disable-decoder=subviewer',
 | 
			
		||||
        '--disable-decoder=subviewer1',
 | 
			
		||||
        '--disable-decoder=sunrast',
 | 
			
		||||
        '--disable-decoder=svq1',
 | 
			
		||||
        '--disable-decoder=svq3',
 | 
			
		||||
        '--disable-decoder=targa',
 | 
			
		||||
        '--disable-decoder=targa_y216',
 | 
			
		||||
        '--disable-decoder=text',
 | 
			
		||||
        '--disable-decoder=tiff',
 | 
			
		||||
        '--disable-decoder=tiertexseqvideo',
 | 
			
		||||
        '--disable-decoder=tmv',
 | 
			
		||||
        '--disable-decoder=truemotion1',
 | 
			
		||||
        '--disable-decoder=truemotion2',
 | 
			
		||||
        '--disable-decoder=truemotion2rt',
 | 
			
		||||
        '--disable-decoder=tscc',
 | 
			
		||||
        '--disable-decoder=tscc2',
 | 
			
		||||
        '--disable-decoder=twinvq',
 | 
			
		||||
        '--disable-decoder=txd',
 | 
			
		||||
        '--disable-decoder=ulti',
 | 
			
		||||
        '--disable-decoder=utvideo',
 | 
			
		||||
        '--disable-decoder=v210',
 | 
			
		||||
        '--disable-decoder=v210x',
 | 
			
		||||
        '--disable-decoder=v308',
 | 
			
		||||
        '--disable-decoder=v408',
 | 
			
		||||
        '--disable-decoder=v410',
 | 
			
		||||
        '--disable-decoder=vb',
 | 
			
		||||
        '--disable-decoder=vble',
 | 
			
		||||
        '--disable-decoder=vbn',
 | 
			
		||||
        '--disable-decoder=vc1',
 | 
			
		||||
        '--disable-decoder=vcr1',
 | 
			
		||||
        '--disable-decoder=vmdvideo',
 | 
			
		||||
        '--disable-decoder=vmnc',
 | 
			
		||||
        '--disable-decoder=vp3',
 | 
			
		||||
        '--disable-decoder=vp5',
 | 
			
		||||
        '--disable-decoder=vp6',
 | 
			
		||||
        '--disable-decoder=vp7',
 | 
			
		||||
        '--disable-decoder=vp8',
 | 
			
		||||
        '--disable-decoder=vp9',
 | 
			
		||||
        '--disable-decoder=vplayer',
 | 
			
		||||
        '--disable-decoder=vqa',
 | 
			
		||||
        '--disable-decoder=webvtt',
 | 
			
		||||
        '--disable-decoder=wcmv',
 | 
			
		||||
        '--disable-decoder=wmv1',
 | 
			
		||||
        '--disable-decoder=wmv2',
 | 
			
		||||
        '--disable-decoder=wmv3',
 | 
			
		||||
        '--disable-decoder=wnv1',
 | 
			
		||||
        '--disable-decoder=wrapped_avframe',
 | 
			
		||||
        '--disable-decoder=xan_wc3',
 | 
			
		||||
        '--disable-decoder=xan_wc4',
 | 
			
		||||
        '--disable-decoder=xbin',
 | 
			
		||||
        '--disable-decoder=xbm',
 | 
			
		||||
        '--disable-decoder=xface',
 | 
			
		||||
        '--disable-decoder=xl',
 | 
			
		||||
        '--disable-decoder=xpm',
 | 
			
		||||
        '--disable-decoder=xsub',
 | 
			
		||||
        '--disable-decoder=xwd',
 | 
			
		||||
        '--disable-decoder=y41p',
 | 
			
		||||
        '--disable-decoder=ylc',
 | 
			
		||||
        '--disable-decoder=yop',
 | 
			
		||||
        '--disable-decoder=yuv4',
 | 
			
		||||
        '--disable-decoder=zero12v',
 | 
			
		||||
        '--disable-decoder=zerocodec',
 | 
			
		||||
        '--disable-decoder=zlib',
 | 
			
		||||
        '--disable-decoder=zmbv',
 | 
			
		||||
 | 
			
		||||
        '--disable-bsf=av1_frame_merge',
 | 
			
		||||
        '--disable-bsf=av1_frame_split',
 | 
			
		||||
        '--disable-bsf=av1_metadata',
 | 
			
		||||
        '--disable-bsf=dts2pts',
 | 
			
		||||
        '--disable-bsf=h264_metadata',
 | 
			
		||||
        '--disable-bsf=h264_mp4toannexb',
 | 
			
		||||
        '--disable-bsf=h264_redundant_pps',
 | 
			
		||||
        '--disable-bsf=hevc_metadata',
 | 
			
		||||
        '--disable-bsf=hevc_mp4toannexb',
 | 
			
		||||
        '--disable-bsf=mjpeg2jpeg',
 | 
			
		||||
        '--disable-bsf=opus_metadata',
 | 
			
		||||
        '--disable-bsf=pgs_frame_merge',
 | 
			
		||||
        '--disable-bsf=text2movsub',
 | 
			
		||||
        '--disable-bsf=vp9_metadata',
 | 
			
		||||
        '--disable-bsf=vp9_raw_reorder',
 | 
			
		||||
        '--disable-bsf=vp9_superframe',
 | 
			
		||||
        '--disable-bsf=vp9_superframe_split',
 | 
			
		||||
    ],
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
openssl = OpenSSLProject(
 | 
			
		||||
    'https://www.openssl.org/source/openssl-3.0.1.tar.gz',
 | 
			
		||||
    'c311ad853353bce796edad01a862c50a8a587f62e7e2100ef465ab53ec9b06d1',
 | 
			
		||||
    ('https://www.openssl.org/source/openssl-3.1.4.tar.gz',
 | 
			
		||||
     'https://artfiles.org/openssl.org/source/openssl-3.1.4.tar.gz'),
 | 
			
		||||
    '840af5366ab9b522bde525826be3ef0fb0af81c6a9ebd84caa600fea1731eee3',
 | 
			
		||||
    'include/openssl/ossl_typ.h',
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
curl = CmakeProject(
 | 
			
		||||
    'https://curl.se/download/curl-7.82.0.tar.xz',
 | 
			
		||||
    '0aaa12d7bd04b0966254f2703ce80dd5c38dbbd76af0297d3d690cdce58a583c',
 | 
			
		||||
    ('https://curl.se/download/curl-8.5.0.tar.xz',
 | 
			
		||||
     'https://github.com/curl/curl/releases/download/curl-8_5_0/curl-8.5.0.tar.xz'),
 | 
			
		||||
    '42ab8db9e20d8290a3b633e7fbb3cec15db34df65fd1015ef8ac1e4723750eeb',
 | 
			
		||||
    'lib/libcurl.a',
 | 
			
		||||
    [
 | 
			
		||||
        '-DBUILD_CURL_EXE=OFF',
 | 
			
		||||
        '-DBUILD_SHARED_LIBS=OFF',
 | 
			
		||||
        '-DCURL_DISABLE_VERBOSE_STRINGS=ON',
 | 
			
		||||
        '-DCURL_DISABLE_LDAP=ON',
 | 
			
		||||
        '-DCURL_DISABLE_TELNET=ON',
 | 
			
		||||
        '-DCURL_DISABLE_DICT=ON',
 | 
			
		||||
@@ -421,8 +644,8 @@ curl = CmakeProject(
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
libnfs = AutotoolsProject(
 | 
			
		||||
    'https://github.com/sahlberg/libnfs/archive/libnfs-5.0.1.tar.gz',
 | 
			
		||||
    '7ef445410b42f36b9bad426608b53ccb9ccca4101e545c383f564c11db672ca8',
 | 
			
		||||
    'https://github.com/sahlberg/libnfs/archive/libnfs-5.0.2.tar.gz',
 | 
			
		||||
    '637e56643b19da9fba98f06847788c4dad308b723156a64748041035dcdf9bd3',
 | 
			
		||||
    'lib/libnfs.a',
 | 
			
		||||
    [
 | 
			
		||||
        '--disable-shared', '--enable-static',
 | 
			
		||||
@@ -433,7 +656,7 @@ libnfs = AutotoolsProject(
 | 
			
		||||
 | 
			
		||||
        '--disable-utils', '--disable-examples',
 | 
			
		||||
    ],
 | 
			
		||||
    base='libnfs-libnfs-5.0.1',
 | 
			
		||||
    base='libnfs-libnfs-5.0.2',
 | 
			
		||||
    autoreconf=True,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -444,7 +667,7 @@ jack = JackProject(
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
boost = BoostProject(
 | 
			
		||||
    'https://boostorg.jfrog.io/artifactory/main/release/1.78.0/source/boost_1_78_0.tar.bz2',
 | 
			
		||||
    '8681f175d4bdb26c52222665793eef08490d7758529330f98d3b29dd0735bccc',
 | 
			
		||||
    'https://boostorg.jfrog.io/artifactory/main/release/1.81.0/source/boost_1_81_0.tar.bz2',
 | 
			
		||||
    '71feeed900fbccca04a3b4f2f84a7c217186f28a940ed8b7ed4725986baf99fa',
 | 
			
		||||
    'include/boost/version.hpp',
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,28 +1,35 @@
 | 
			
		||||
import subprocess
 | 
			
		||||
import subprocess, multiprocessing
 | 
			
		||||
from typing import Optional, Sequence, Union
 | 
			
		||||
 | 
			
		||||
from build.project import Project
 | 
			
		||||
from .toolchain import AnyToolchain
 | 
			
		||||
 | 
			
		||||
class MakeProject(Project):
 | 
			
		||||
    def __init__(self, url, md5, installed,
 | 
			
		||||
                 install_target='install',
 | 
			
		||||
    def __init__(self, url: Union[str, Sequence[str]], md5: str, installed: str,
 | 
			
		||||
                 install_target: str='install',
 | 
			
		||||
                 **kwargs):
 | 
			
		||||
        Project.__init__(self, url, md5, installed, **kwargs)
 | 
			
		||||
        self.install_target = install_target
 | 
			
		||||
 | 
			
		||||
    def get_simultaneous_jobs(self):
 | 
			
		||||
        return 12
 | 
			
		||||
    def get_simultaneous_jobs(self) -> int:
 | 
			
		||||
        try:
 | 
			
		||||
            # use twice as many simultaneous jobs as we have CPU cores
 | 
			
		||||
            return multiprocessing.cpu_count() * 2
 | 
			
		||||
        except NotImplementedError:
 | 
			
		||||
            # default to 12, if multiprocessing.cpu_count() is not implemented
 | 
			
		||||
            return 12
 | 
			
		||||
 | 
			
		||||
    def get_make_args(self, toolchain):
 | 
			
		||||
    def get_make_args(self, toolchain: AnyToolchain) -> list[str]:
 | 
			
		||||
        return ['--quiet', '-j' + str(self.get_simultaneous_jobs())]
 | 
			
		||||
 | 
			
		||||
    def get_make_install_args(self, toolchain):
 | 
			
		||||
    def get_make_install_args(self, toolchain: AnyToolchain) -> list[str]:
 | 
			
		||||
        return ['--quiet', self.install_target]
 | 
			
		||||
 | 
			
		||||
    def make(self, toolchain, wd, args):
 | 
			
		||||
        subprocess.check_call(['/usr/bin/make'] + args,
 | 
			
		||||
    def make(self, toolchain: AnyToolchain, wd: str, args: list[str]) -> None:
 | 
			
		||||
        subprocess.check_call(['make'] + args,
 | 
			
		||||
                              cwd=wd, env=toolchain.env)
 | 
			
		||||
 | 
			
		||||
    def build_make(self, toolchain, wd, install=True):
 | 
			
		||||
    def build_make(self, toolchain: AnyToolchain, wd: str, install: bool=True) -> None:
 | 
			
		||||
        self.make(toolchain, wd, self.get_make_args(toolchain))
 | 
			
		||||
        if install:
 | 
			
		||||
            self.make(toolchain, wd, self.get_make_install_args(toolchain))
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,17 @@
 | 
			
		||||
import os.path, subprocess, sys
 | 
			
		||||
import os
 | 
			
		||||
import subprocess
 | 
			
		||||
import platform
 | 
			
		||||
from typing import Optional, Sequence, Union
 | 
			
		||||
 | 
			
		||||
from build.project import Project
 | 
			
		||||
from .toolchain import AnyToolchain
 | 
			
		||||
 | 
			
		||||
def make_cross_file(toolchain):
 | 
			
		||||
def __no_ccache(cmd: str) -> str:
 | 
			
		||||
    if cmd.startswith('ccache '):
 | 
			
		||||
        cmd = cmd[7:]
 | 
			
		||||
    return cmd
 | 
			
		||||
 | 
			
		||||
def make_cross_file(toolchain: AnyToolchain) -> str:
 | 
			
		||||
    if toolchain.is_windows:
 | 
			
		||||
        system = 'windows'
 | 
			
		||||
        windres = "windres = '%s'" % toolchain.windres
 | 
			
		||||
@@ -22,7 +30,7 @@ def make_cross_file(toolchain):
 | 
			
		||||
        cpu = 'arm64-v8a'
 | 
			
		||||
    else:
 | 
			
		||||
        cpu_family = 'x86'
 | 
			
		||||
        if 'x86_64' in toolchain.arch:
 | 
			
		||||
        if 'x86_64' in toolchain.host_triplet:
 | 
			
		||||
            cpu = 'x86_64'
 | 
			
		||||
        else:
 | 
			
		||||
            cpu = 'i686'
 | 
			
		||||
@@ -37,8 +45,8 @@ def make_cross_file(toolchain):
 | 
			
		||||
    with open(path, 'w') as f:
 | 
			
		||||
        f.write(f"""
 | 
			
		||||
[binaries]
 | 
			
		||||
c = '{toolchain.cc}'
 | 
			
		||||
cpp = '{toolchain.cxx}'
 | 
			
		||||
c = '{__no_ccache(toolchain.cc)}'
 | 
			
		||||
cpp = '{__no_ccache(toolchain.cxx)}'
 | 
			
		||||
ar = '{toolchain.ar}'
 | 
			
		||||
strip = '{toolchain.strip}'
 | 
			
		||||
pkgconfig = '{toolchain.pkg_config}'
 | 
			
		||||
@@ -55,7 +63,7 @@ pkgconfig = '{toolchain.pkg_config}'
 | 
			
		||||
root = '{toolchain.install_prefix}'
 | 
			
		||||
""")
 | 
			
		||||
 | 
			
		||||
        if 'android' in toolchain.arch:
 | 
			
		||||
        if toolchain.is_android:
 | 
			
		||||
            f.write("""
 | 
			
		||||
# Keep Meson from executing Android-x86 test binariees
 | 
			
		||||
needs_exe_wrapper = true
 | 
			
		||||
@@ -79,21 +87,23 @@ endian = '{endian}'
 | 
			
		||||
""")
 | 
			
		||||
    return path
 | 
			
		||||
 | 
			
		||||
def configure(toolchain, src, build, args=()):
 | 
			
		||||
    cross_file = make_cross_file(toolchain)
 | 
			
		||||
def configure(toolchain: AnyToolchain, src: str, build: str, args: list[str]=[]) -> None:
 | 
			
		||||
    configure = [
 | 
			
		||||
        'meson',
 | 
			
		||||
        src, build,
 | 
			
		||||
        'meson', 'setup',
 | 
			
		||||
        build, src,
 | 
			
		||||
 | 
			
		||||
        '--prefix', toolchain.install_prefix,
 | 
			
		||||
 | 
			
		||||
        '--buildtype', 'plain',
 | 
			
		||||
 | 
			
		||||
        '--default-library=static',
 | 
			
		||||
 | 
			
		||||
        '--cross-file', cross_file,
 | 
			
		||||
    ] + args
 | 
			
		||||
 | 
			
		||||
    if toolchain.host_triplet is not None:
 | 
			
		||||
        # cross-compiling: write a cross-file
 | 
			
		||||
        cross_file = make_cross_file(toolchain)
 | 
			
		||||
        configure.append(f'--cross-file={cross_file}')
 | 
			
		||||
 | 
			
		||||
    env = toolchain.env.copy()
 | 
			
		||||
 | 
			
		||||
    # Meson 0.54 requires the BOOST_ROOT environment variable
 | 
			
		||||
@@ -102,18 +112,19 @@ def configure(toolchain, src, build, args=()):
 | 
			
		||||
    subprocess.check_call(configure, env=env)
 | 
			
		||||
 | 
			
		||||
class MesonProject(Project):
 | 
			
		||||
    def __init__(self, url, md5, installed, configure_args=[],
 | 
			
		||||
    def __init__(self, url: Union[str, Sequence[str]], md5: str, installed: str,
 | 
			
		||||
                 configure_args: list[str]=[],
 | 
			
		||||
                 **kwargs):
 | 
			
		||||
        Project.__init__(self, url, md5, installed, **kwargs)
 | 
			
		||||
        self.configure_args = configure_args
 | 
			
		||||
 | 
			
		||||
    def configure(self, toolchain):
 | 
			
		||||
    def configure(self, toolchain: AnyToolchain) -> str:
 | 
			
		||||
        src = self.unpack(toolchain)
 | 
			
		||||
        build = self.make_build_path(toolchain)
 | 
			
		||||
        configure(toolchain, src, build, self.configure_args)
 | 
			
		||||
        return build
 | 
			
		||||
 | 
			
		||||
    def _build(self, toolchain):
 | 
			
		||||
    def _build(self, toolchain: AnyToolchain) -> None:
 | 
			
		||||
        build = self.configure(toolchain)
 | 
			
		||||
        subprocess.check_call(['ninja', 'install'],
 | 
			
		||||
        subprocess.check_call(['ninja', '-v', 'install'],
 | 
			
		||||
                              cwd=build, env=toolchain.env)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,15 @@
 | 
			
		||||
import subprocess
 | 
			
		||||
from typing import Optional, Sequence, Union
 | 
			
		||||
 | 
			
		||||
from build.makeproject import MakeProject
 | 
			
		||||
from .toolchain import AnyToolchain
 | 
			
		||||
 | 
			
		||||
class OpenSSLProject(MakeProject):
 | 
			
		||||
    def __init__(self, url, md5, installed,
 | 
			
		||||
    def __init__(self, url: Union[str, Sequence[str]], md5: str, installed: str,
 | 
			
		||||
                 **kwargs):
 | 
			
		||||
        MakeProject.__init__(self, url, md5, installed, install_target='install_dev', **kwargs)
 | 
			
		||||
 | 
			
		||||
    def get_make_args(self, toolchain):
 | 
			
		||||
    def get_make_args(self, toolchain: AnyToolchain) -> list[str]:
 | 
			
		||||
        return MakeProject.get_make_args(self, toolchain) + [
 | 
			
		||||
            'CC=' + toolchain.cc,
 | 
			
		||||
            'CFLAGS=' + toolchain.cflags,
 | 
			
		||||
@@ -17,45 +19,60 @@ class OpenSSLProject(MakeProject):
 | 
			
		||||
            'build_libs',
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
    def get_make_install_args(self, toolchain):
 | 
			
		||||
    def get_make_install_args(self, toolchain: AnyToolchain) -> list[str]:
 | 
			
		||||
        # OpenSSL's Makefile runs "ranlib" during installation
 | 
			
		||||
        return MakeProject.get_make_install_args(self, toolchain) + [
 | 
			
		||||
            'RANLIB=' + toolchain.ranlib,
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
    def _build(self, toolchain):
 | 
			
		||||
    def _build(self, toolchain: AnyToolchain) -> None:
 | 
			
		||||
        src = self.unpack(toolchain, out_of_tree=False)
 | 
			
		||||
 | 
			
		||||
        # OpenSSL has a weird target architecture scheme with lots of
 | 
			
		||||
        # hard-coded architectures; this table translates between our
 | 
			
		||||
        # "toolchain_arch" (HOST_TRIPLET) and the OpenSSL target
 | 
			
		||||
        # host triplet and the OpenSSL target
 | 
			
		||||
        openssl_archs = {
 | 
			
		||||
            # not using "android-*" because those OpenSSL targets want
 | 
			
		||||
            # to know where the SDK is, but our own build scripts
 | 
			
		||||
            # prepared everything already to look like a regular Linux
 | 
			
		||||
            # build
 | 
			
		||||
            'arm-linux-androideabi': 'linux-generic32',
 | 
			
		||||
            'armv7a-linux-androideabi': 'linux-generic32',
 | 
			
		||||
            'aarch64-linux-android': 'linux-aarch64',
 | 
			
		||||
            'i686-linux-android': 'linux-x86-clang',
 | 
			
		||||
            'x86_64-linux-android': 'linux-x86_64-clang',
 | 
			
		||||
 | 
			
		||||
            # Kobo
 | 
			
		||||
            # generic Linux
 | 
			
		||||
            'arm-linux-gnueabihf': 'linux-generic32',
 | 
			
		||||
 | 
			
		||||
            # Windows
 | 
			
		||||
            'i686-w64-mingw32': 'mingw',
 | 
			
		||||
            'x86_64-w64-mingw32': 'mingw64',
 | 
			
		||||
 | 
			
		||||
            # Apple
 | 
			
		||||
            'x86_64-apple-darwin': 'darwin64-x86_64-cc',
 | 
			
		||||
            'aarch64-apple-darwin': 'darwin64-arm64-cc',
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        openssl_arch = openssl_archs[toolchain.arch]
 | 
			
		||||
        configure = [
 | 
			
		||||
            './Configure',
 | 
			
		||||
            'no-shared',
 | 
			
		||||
            'no-module',
 | 
			
		||||
            'no-engine',
 | 
			
		||||
            'no-static-engine',
 | 
			
		||||
            'no-async',
 | 
			
		||||
            'no-tests',
 | 
			
		||||
            'no-makedepend',
 | 
			
		||||
            '--libdir=lib', # no "lib64" on amd64, please
 | 
			
		||||
            '--prefix=' + toolchain.install_prefix,
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        subprocess.check_call(['./Configure',
 | 
			
		||||
                               'no-shared',
 | 
			
		||||
                               'no-module', 'no-engine', 'no-static-engine',
 | 
			
		||||
                               'no-async',
 | 
			
		||||
                               'no-tests',
 | 
			
		||||
                               'no-asm', # "asm" causes build failures on Windows
 | 
			
		||||
                               openssl_arch,
 | 
			
		||||
                               '--prefix=' + toolchain.install_prefix],
 | 
			
		||||
                              cwd=src, env=toolchain.env)
 | 
			
		||||
        if toolchain.is_windows:
 | 
			
		||||
            # workaround for build failures
 | 
			
		||||
            configure.append('no-asm')
 | 
			
		||||
 | 
			
		||||
        if toolchain.host_triplet is not None:
 | 
			
		||||
            configure.append(openssl_archs[toolchain.host_triplet])
 | 
			
		||||
            configure.append(f'--cross-compile-prefix={toolchain.host_triplet}-')
 | 
			
		||||
 | 
			
		||||
        subprocess.check_call(configure, cwd=src, env=toolchain.env)
 | 
			
		||||
        self.build_make(toolchain, src)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,26 +1,30 @@
 | 
			
		||||
import os, shutil
 | 
			
		||||
import re
 | 
			
		||||
from typing import cast, BinaryIO, Optional, Sequence, Union
 | 
			
		||||
 | 
			
		||||
from build.download import download_and_verify
 | 
			
		||||
from build.download import download_basename, download_and_verify
 | 
			
		||||
from build.tar import untar
 | 
			
		||||
from build.quilt import push_all
 | 
			
		||||
from .toolchain import AnyToolchain
 | 
			
		||||
 | 
			
		||||
class Project:
 | 
			
		||||
    def __init__(self, url, md5, installed, name=None, version=None,
 | 
			
		||||
                 base=None,
 | 
			
		||||
                 patches=None,
 | 
			
		||||
    def __init__(self, url: Union[str, Sequence[str]], md5: str, installed: str,
 | 
			
		||||
                 name: Optional[str]=None, version: Optional[str]=None,
 | 
			
		||||
                 base: Optional[str]=None,
 | 
			
		||||
                 patches: Optional[str]=None,
 | 
			
		||||
                 edits=None,
 | 
			
		||||
                 use_cxx=False):
 | 
			
		||||
                 use_cxx: bool=False):
 | 
			
		||||
        if base is None:
 | 
			
		||||
            basename = os.path.basename(url)
 | 
			
		||||
            basename = download_basename(url)
 | 
			
		||||
            m = re.match(r'^(.+)\.(tar(\.(gz|bz2|xz|lzma))?|zip)$', basename)
 | 
			
		||||
            if not m: raise
 | 
			
		||||
            if not m: raise RuntimeError('Could not identify tarball name: ' + basename)
 | 
			
		||||
            self.base = m.group(1)
 | 
			
		||||
        else:
 | 
			
		||||
            self.base = base
 | 
			
		||||
 | 
			
		||||
        if name is None or version is None:
 | 
			
		||||
            m = re.match(r'^([-\w]+)-(\d[\d.]*[a-z]?[\d.]*(?:-(?:alpha|beta)\d+)?)(\+.*)?$', self.base)
 | 
			
		||||
            if not m: raise RuntimeError('Could not identify tarball name: ' + self.base)
 | 
			
		||||
            if name is None: name = m.group(1)
 | 
			
		||||
            if version is None: version = m.group(2)
 | 
			
		||||
 | 
			
		||||
@@ -38,10 +42,10 @@ class Project:
 | 
			
		||||
        self.edits = edits
 | 
			
		||||
        self.use_cxx = use_cxx
 | 
			
		||||
 | 
			
		||||
    def download(self, toolchain):
 | 
			
		||||
    def download(self, toolchain: AnyToolchain) -> str:
 | 
			
		||||
        return download_and_verify(self.url, self.md5, toolchain.tarball_path)
 | 
			
		||||
 | 
			
		||||
    def is_installed(self, toolchain):
 | 
			
		||||
    def is_installed(self, toolchain: AnyToolchain) -> bool:
 | 
			
		||||
        tarball = self.download(toolchain)
 | 
			
		||||
        installed = os.path.join(toolchain.install_prefix, self.installed)
 | 
			
		||||
        tarball_mtime = os.path.getmtime(tarball)
 | 
			
		||||
@@ -50,13 +54,13 @@ class Project:
 | 
			
		||||
        except FileNotFoundError:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
    def unpack(self, toolchain, out_of_tree=True):
 | 
			
		||||
    def unpack(self, toolchain: AnyToolchain, out_of_tree: bool=True) -> str:
 | 
			
		||||
        if out_of_tree:
 | 
			
		||||
            parent_path = toolchain.src_path
 | 
			
		||||
        else:
 | 
			
		||||
            parent_path = toolchain.build_path
 | 
			
		||||
        path = untar(self.download(toolchain), parent_path, self.base)
 | 
			
		||||
 | 
			
		||||
        path = untar(self.download(toolchain), parent_path, self.base,
 | 
			
		||||
                     lazy=out_of_tree and self.patches is None)
 | 
			
		||||
        if self.patches is not None:
 | 
			
		||||
            push_all(toolchain, path, self.patches)
 | 
			
		||||
 | 
			
		||||
@@ -71,8 +75,10 @@ class Project:
 | 
			
		||||
 | 
			
		||||
        return path
 | 
			
		||||
 | 
			
		||||
    def make_build_path(self, toolchain):
 | 
			
		||||
    def make_build_path(self, toolchain: AnyToolchain, lazy: bool=False) -> str:
 | 
			
		||||
        path = os.path.join(toolchain.build_path, self.base)
 | 
			
		||||
        if lazy and os.path.isdir(path):
 | 
			
		||||
            return path
 | 
			
		||||
        try:
 | 
			
		||||
            shutil.rmtree(path)
 | 
			
		||||
        except FileNotFoundError:
 | 
			
		||||
@@ -80,5 +86,5 @@ class Project:
 | 
			
		||||
        os.makedirs(path, exist_ok=True)
 | 
			
		||||
        return path
 | 
			
		||||
 | 
			
		||||
    def build(self, toolchain):
 | 
			
		||||
    def build(self, toolchain: AnyToolchain) -> None:
 | 
			
		||||
        self._build(toolchain)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,12 @@
 | 
			
		||||
import subprocess
 | 
			
		||||
from typing import Union
 | 
			
		||||
 | 
			
		||||
def run_quilt(toolchain, cwd, patches_path, *args):
 | 
			
		||||
from .toolchain import AnyToolchain
 | 
			
		||||
 | 
			
		||||
def run_quilt(toolchain: AnyToolchain, cwd: str, patches_path: str, *args: str) -> None:
 | 
			
		||||
    env = dict(toolchain.env)
 | 
			
		||||
    env['QUILT_PATCHES'] = patches_path
 | 
			
		||||
    subprocess.check_call(['quilt'] + list(args), cwd=cwd, env=env)
 | 
			
		||||
 | 
			
		||||
def push_all(toolchain, src_path, patches_path):
 | 
			
		||||
def push_all(toolchain: AnyToolchain, src_path: str, patches_path: str) -> None:
 | 
			
		||||
    run_quilt(toolchain, src_path, patches_path, 'push', '-a')
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,17 @@
 | 
			
		||||
import os, shutil, subprocess
 | 
			
		||||
 | 
			
		||||
def untar(tarball_path, parent_path, base):
 | 
			
		||||
def untar(tarball_path: str, parent_path: str, base: str,
 | 
			
		||||
          lazy: bool=False) -> str:
 | 
			
		||||
    path = os.path.join(parent_path, base)
 | 
			
		||||
    if lazy and os.path.isdir(path):
 | 
			
		||||
        return path
 | 
			
		||||
    try:
 | 
			
		||||
        shutil.rmtree(path)
 | 
			
		||||
    except FileNotFoundError:
 | 
			
		||||
        pass
 | 
			
		||||
    os.makedirs(parent_path, exist_ok=True)
 | 
			
		||||
    try:
 | 
			
		||||
        subprocess.check_call(['/bin/tar', 'xfC', tarball_path, parent_path])
 | 
			
		||||
        subprocess.check_call(['tar', 'xfC', tarball_path, parent_path])
 | 
			
		||||
    except FileNotFoundError:
 | 
			
		||||
        import tarfile
 | 
			
		||||
        tar = tarfile.open(tarball_path)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										175
									
								
								python/build/toolchain.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										175
									
								
								python/build/toolchain.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,175 @@
 | 
			
		||||
import os.path
 | 
			
		||||
import shutil
 | 
			
		||||
from typing import Union
 | 
			
		||||
 | 
			
		||||
android_abis = {
 | 
			
		||||
    'armeabi-v7a': {
 | 
			
		||||
        'arch': 'armv7a-linux-androideabi',
 | 
			
		||||
        'ndk_arch': 'arm',
 | 
			
		||||
        'cflags': '-fpic -mfpu=neon -mfloat-abi=softfp',
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    'arm64-v8a': {
 | 
			
		||||
        'arch': 'aarch64-linux-android',
 | 
			
		||||
        'ndk_arch': 'arm64',
 | 
			
		||||
        'cflags': '-fpic',
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    'x86': {
 | 
			
		||||
        'arch': 'i686-linux-android',
 | 
			
		||||
        'ndk_arch': 'x86',
 | 
			
		||||
        'cflags': '-fPIC -march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32',
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    'x86_64': {
 | 
			
		||||
        'arch': 'x86_64-linux-android',
 | 
			
		||||
        'ndk_arch': 'x86_64',
 | 
			
		||||
        'cflags': '-fPIC -m64',
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class AndroidNdkToolchain:
 | 
			
		||||
    def __init__(self, top_path: str, lib_path: str,
 | 
			
		||||
                 tarball_path: str, src_path: str,
 | 
			
		||||
                 ndk_path: str, android_abi: str,
 | 
			
		||||
                 use_cxx):
 | 
			
		||||
        # build host configuration
 | 
			
		||||
        build_arch = 'linux-x86_64'
 | 
			
		||||
 | 
			
		||||
        # select the NDK target
 | 
			
		||||
        abi_info = android_abis[android_abi]
 | 
			
		||||
        host_triplet = abi_info['arch']
 | 
			
		||||
 | 
			
		||||
        arch_path = os.path.join(lib_path, host_triplet)
 | 
			
		||||
 | 
			
		||||
        self.tarball_path = tarball_path
 | 
			
		||||
        self.src_path = src_path
 | 
			
		||||
        self.build_path = os.path.join(arch_path, 'build')
 | 
			
		||||
 | 
			
		||||
        ndk_arch = abi_info['ndk_arch']
 | 
			
		||||
        android_api_level = '24'
 | 
			
		||||
 | 
			
		||||
        install_prefix = os.path.join(arch_path, 'root')
 | 
			
		||||
 | 
			
		||||
        self.host_triplet = host_triplet
 | 
			
		||||
        self.install_prefix = install_prefix
 | 
			
		||||
 | 
			
		||||
        llvm_path = os.path.join(ndk_path, 'toolchains', 'llvm', 'prebuilt', build_arch)
 | 
			
		||||
        llvm_triple = host_triplet + android_api_level
 | 
			
		||||
 | 
			
		||||
        common_flags = '-Os -g'
 | 
			
		||||
        common_flags += ' ' + abi_info['cflags']
 | 
			
		||||
 | 
			
		||||
        llvm_bin = os.path.join(llvm_path, 'bin')
 | 
			
		||||
        self.cc = os.path.join(llvm_bin, 'clang')
 | 
			
		||||
        self.cxx = os.path.join(llvm_bin, 'clang++')
 | 
			
		||||
        common_flags += ' -target ' + llvm_triple
 | 
			
		||||
 | 
			
		||||
        common_flags += ' -fvisibility=hidden -fdata-sections -ffunction-sections'
 | 
			
		||||
 | 
			
		||||
        self.ar = os.path.join(llvm_bin, 'llvm-ar')
 | 
			
		||||
        self.arflags = 'rcs'
 | 
			
		||||
        self.ranlib = os.path.join(llvm_bin, 'llvm-ranlib')
 | 
			
		||||
        self.nm = os.path.join(llvm_bin, 'llvm-nm')
 | 
			
		||||
        self.strip = os.path.join(llvm_bin, 'llvm-strip')
 | 
			
		||||
 | 
			
		||||
        self.cflags = common_flags
 | 
			
		||||
        self.cxxflags = common_flags
 | 
			
		||||
        self.cppflags = ' -isystem ' + os.path.join(install_prefix, 'include')
 | 
			
		||||
        self.ldflags = ' -L' + os.path.join(install_prefix, 'lib') + \
 | 
			
		||||
            ' -Wl,--exclude-libs=ALL' + \
 | 
			
		||||
            ' ' + common_flags
 | 
			
		||||
        self.ldflags = common_flags
 | 
			
		||||
        self.libs = ''
 | 
			
		||||
 | 
			
		||||
        self.is_arm = ndk_arch == 'arm'
 | 
			
		||||
        self.is_armv7 = self.is_arm and 'armv7' in self.cflags
 | 
			
		||||
        self.is_aarch64 = ndk_arch == 'arm64'
 | 
			
		||||
        self.is_windows = False
 | 
			
		||||
        self.is_android = True
 | 
			
		||||
        self.is_darwin = False
 | 
			
		||||
 | 
			
		||||
        libstdcxx_flags = ''
 | 
			
		||||
        libstdcxx_cxxflags = ''
 | 
			
		||||
        libstdcxx_ldflags = ''
 | 
			
		||||
        libstdcxx_libs = '-static-libstdc++'
 | 
			
		||||
 | 
			
		||||
        if self.is_armv7:
 | 
			
		||||
            # On 32 bit ARM, clang generates no ".eh_frame" section;
 | 
			
		||||
            # instead, the LLVM unwinder library is used for unwinding
 | 
			
		||||
            # the stack after a C++ exception was thrown
 | 
			
		||||
            libstdcxx_libs += ' -lunwind'
 | 
			
		||||
 | 
			
		||||
        if use_cxx:
 | 
			
		||||
            self.cxxflags += ' ' + libstdcxx_cxxflags
 | 
			
		||||
            self.ldflags += ' ' + libstdcxx_ldflags
 | 
			
		||||
            self.libs += ' ' + libstdcxx_libs
 | 
			
		||||
 | 
			
		||||
        self.env = dict(os.environ)
 | 
			
		||||
 | 
			
		||||
        # redirect pkg-config to use our root directory instead of the
 | 
			
		||||
        # default one on the build host
 | 
			
		||||
        bin_dir = os.path.join(install_prefix, 'bin')
 | 
			
		||||
        os.makedirs(bin_dir, exist_ok=True)
 | 
			
		||||
        self.pkg_config = shutil.copy(os.path.join(top_path, 'build', 'pkg-config.sh'),
 | 
			
		||||
                                      os.path.join(bin_dir, 'pkg-config'))
 | 
			
		||||
        self.env['PKG_CONFIG'] = self.pkg_config
 | 
			
		||||
 | 
			
		||||
class MingwToolchain:
 | 
			
		||||
    def __init__(self, top_path: str,
 | 
			
		||||
                 toolchain_path, host_triplet, x64: bool,
 | 
			
		||||
                 tarball_path, src_path, build_path, install_prefix):
 | 
			
		||||
        self.host_triplet = host_triplet
 | 
			
		||||
        self.tarball_path = tarball_path
 | 
			
		||||
        self.src_path = src_path
 | 
			
		||||
        self.build_path = build_path
 | 
			
		||||
        self.install_prefix = install_prefix
 | 
			
		||||
 | 
			
		||||
        toolchain_bin = os.path.join(toolchain_path, 'bin')
 | 
			
		||||
        self.cc = os.path.join(toolchain_bin, host_triplet + '-gcc')
 | 
			
		||||
        self.cxx = os.path.join(toolchain_bin, host_triplet + '-g++')
 | 
			
		||||
        self.ar = os.path.join(toolchain_bin, host_triplet + '-ar')
 | 
			
		||||
        self.arflags = 'rcs'
 | 
			
		||||
        self.ranlib = os.path.join(toolchain_bin, host_triplet + '-ranlib')
 | 
			
		||||
        self.nm = os.path.join(toolchain_bin, host_triplet + '-nm')
 | 
			
		||||
        self.strip = os.path.join(toolchain_bin, host_triplet + '-strip')
 | 
			
		||||
        self.windres = os.path.join(toolchain_bin, host_triplet + '-windres')
 | 
			
		||||
 | 
			
		||||
        common_flags = '-O2 -g'
 | 
			
		||||
 | 
			
		||||
        if not x64:
 | 
			
		||||
            # enable SSE support which is required for LAME
 | 
			
		||||
            common_flags += ' -march=pentium3'
 | 
			
		||||
 | 
			
		||||
        self.cflags = common_flags
 | 
			
		||||
        self.cxxflags = common_flags
 | 
			
		||||
        self.cppflags = '-isystem ' + os.path.join(install_prefix, 'include') + \
 | 
			
		||||
                        ' -DWINVER=0x0600 -D_WIN32_WINNT=0x0600'
 | 
			
		||||
        self.ldflags = '-L' + os.path.join(install_prefix, 'lib') + \
 | 
			
		||||
                       ' -static-libstdc++ -static-libgcc'
 | 
			
		||||
        self.libs = ''
 | 
			
		||||
 | 
			
		||||
        # Explicitly disable _FORTIFY_SOURCE because it is broken with
 | 
			
		||||
        # mingw.  This prevents some libraries such as libFLAC to
 | 
			
		||||
        # enable it.
 | 
			
		||||
        self.cppflags += ' -D_FORTIFY_SOURCE=0'
 | 
			
		||||
 | 
			
		||||
        self.is_arm = host_triplet.startswith('arm')
 | 
			
		||||
        self.is_armv7 = self.is_arm and 'armv7' in self.cflags
 | 
			
		||||
        self.is_aarch64 = host_triplet == 'aarch64'
 | 
			
		||||
        self.is_windows = 'mingw32' in host_triplet
 | 
			
		||||
        self.is_android = False
 | 
			
		||||
        self.is_darwin = False
 | 
			
		||||
 | 
			
		||||
        self.env = dict(os.environ)
 | 
			
		||||
 | 
			
		||||
        # redirect pkg-config to use our root directory instead of the
 | 
			
		||||
        # default one on the build host
 | 
			
		||||
        import shutil
 | 
			
		||||
        bin_dir = os.path.join(install_prefix, 'bin')
 | 
			
		||||
        os.makedirs(bin_dir, exist_ok=True)
 | 
			
		||||
        self.pkg_config = shutil.copy(os.path.join(top_path, 'build', 'pkg-config.sh'),
 | 
			
		||||
                                      os.path.join(bin_dir, 'pkg-config'))
 | 
			
		||||
        self.env['PKG_CONFIG'] = self.pkg_config
 | 
			
		||||
 | 
			
		||||
AnyToolchain = Union[AndroidNdkToolchain, MingwToolchain]
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
import hashlib
 | 
			
		||||
from typing import cast, Any, BinaryIO
 | 
			
		||||
 | 
			
		||||
def feed_file(h, f):
 | 
			
		||||
def feed_file(h: Any, f: BinaryIO) -> None:
 | 
			
		||||
    """Feed data read from an open file into the hashlib instance."""
 | 
			
		||||
 | 
			
		||||
    while True:
 | 
			
		||||
@@ -10,20 +11,20 @@ def feed_file(h, f):
 | 
			
		||||
            break
 | 
			
		||||
        h.update(data)
 | 
			
		||||
 | 
			
		||||
def feed_file_path(h, path):
 | 
			
		||||
def feed_file_path(h: Any, path: str) -> None:
 | 
			
		||||
    """Feed data read from a file (to be opened by this function) into the hashlib instance."""
 | 
			
		||||
 | 
			
		||||
    with open(path, 'rb') as f:
 | 
			
		||||
        feed_file(h, f)
 | 
			
		||||
 | 
			
		||||
def file_digest(algorithm, path):
 | 
			
		||||
def file_digest(algorithm: Any, path: str) -> str:
 | 
			
		||||
    """Calculate the digest of a file and return it in hexadecimal notation."""
 | 
			
		||||
 | 
			
		||||
    h = algorithm()
 | 
			
		||||
    feed_file_path(h, path)
 | 
			
		||||
    return h.hexdigest()
 | 
			
		||||
    return cast(str, h.hexdigest())
 | 
			
		||||
 | 
			
		||||
def guess_digest_algorithm(digest):
 | 
			
		||||
def guess_digest_algorithm(digest: str) -> Any:
 | 
			
		||||
    l = len(digest)
 | 
			
		||||
    if l == 32:
 | 
			
		||||
        return hashlib.md5
 | 
			
		||||
@@ -36,7 +37,7 @@ def guess_digest_algorithm(digest):
 | 
			
		||||
    else:
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
def verify_file_digest(path, expected_digest):
 | 
			
		||||
def verify_file_digest(path: str, expected_digest: str) -> bool:
 | 
			
		||||
    """Verify the digest of a file, and return True if the digest matches with the given expected digest."""
 | 
			
		||||
 | 
			
		||||
    algorithm = guess_digest_algorithm(expected_digest)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,22 +1,34 @@
 | 
			
		||||
import os.path, subprocess
 | 
			
		||||
import subprocess
 | 
			
		||||
from typing import Optional, Sequence, Union
 | 
			
		||||
 | 
			
		||||
from build.project import Project
 | 
			
		||||
from build.makeproject import MakeProject
 | 
			
		||||
from .toolchain import AnyToolchain
 | 
			
		||||
 | 
			
		||||
class ZlibProject(Project):
 | 
			
		||||
    def __init__(self, url, md5, installed,
 | 
			
		||||
class ZlibProject(MakeProject):
 | 
			
		||||
    def __init__(self, url: Union[str, Sequence[str]], md5: str, installed: str,
 | 
			
		||||
                 **kwargs):
 | 
			
		||||
        Project.__init__(self, url, md5, installed, **kwargs)
 | 
			
		||||
        MakeProject.__init__(self, url, md5, installed, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def _build(self, toolchain):
 | 
			
		||||
    def get_make_args(self, toolchain: AnyToolchain) -> list[str]:
 | 
			
		||||
        return MakeProject.get_make_args(self, toolchain) + [
 | 
			
		||||
            'CC=' + toolchain.cc + ' ' + toolchain.cppflags + ' ' + toolchain.cflags,
 | 
			
		||||
            'CPP=' + toolchain.cc + ' -E ' + toolchain.cppflags,
 | 
			
		||||
            'AR=' + toolchain.ar,
 | 
			
		||||
            'ARFLAGS=' + toolchain.arflags,
 | 
			
		||||
            'RANLIB=' + toolchain.ranlib,
 | 
			
		||||
            'LDSHARED=' + toolchain.cc + ' -shared',
 | 
			
		||||
            'libz.a'
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
    def get_make_install_args(self, toolchain: AnyToolchain) -> list[str]:
 | 
			
		||||
        return [
 | 
			
		||||
            'RANLIB=' + toolchain.ranlib,
 | 
			
		||||
            self.install_target
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
    def _build(self, toolchain: AnyToolchain) -> None:
 | 
			
		||||
        src = self.unpack(toolchain, out_of_tree=False)
 | 
			
		||||
 | 
			
		||||
        subprocess.check_call(['/usr/bin/make', '--quiet',
 | 
			
		||||
            '-f', 'win32/Makefile.gcc',
 | 
			
		||||
            'PREFIX=' + toolchain.arch + '-',
 | 
			
		||||
            '-j12',
 | 
			
		||||
            'install',
 | 
			
		||||
            'INCLUDE_PATH='+ os.path.join(toolchain.install_prefix, 'include'),
 | 
			
		||||
            'LIBRARY_PATH=' + os.path.join(toolchain.install_prefix, 'lib'),
 | 
			
		||||
            'BINARY_PATH=' + os.path.join(toolchain.install_prefix, 'bin'),
 | 
			
		||||
            ],
 | 
			
		||||
            cwd=src, env=toolchain.env)
 | 
			
		||||
        subprocess.check_call(['./configure', '--prefix=' + toolchain.install_prefix, '--static'],
 | 
			
		||||
                              cwd=src, env=toolchain.env)
 | 
			
		||||
        self.build_make(toolchain, src)
 | 
			
		||||
 
 | 
			
		||||
@@ -352,12 +352,16 @@ ParseCommandLine(int argc, char **argv, CommandLineOptions &options,
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
		case OPTION_NO_DAEMON:
 | 
			
		||||
#ifdef ENABLE_DAEMON
 | 
			
		||||
			options.daemon = false;
 | 
			
		||||
#endif
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
#ifdef __linux__
 | 
			
		||||
		case OPTION_SYSTEMD:
 | 
			
		||||
#ifdef ENABLE_DAEMON
 | 
			
		||||
			options.daemon = false;
 | 
			
		||||
#endif
 | 
			
		||||
			options.systemd = true;
 | 
			
		||||
			break;
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -20,11 +20,18 @@
 | 
			
		||||
#ifndef MPD_COMMAND_LINE_HXX
 | 
			
		||||
#define MPD_COMMAND_LINE_HXX
 | 
			
		||||
 | 
			
		||||
#include "config.h" // for ENABLE_DAEMON
 | 
			
		||||
 | 
			
		||||
struct ConfigData;
 | 
			
		||||
 | 
			
		||||
struct CommandLineOptions {
 | 
			
		||||
	bool kill = false;
 | 
			
		||||
 | 
			
		||||
#ifdef ENABLE_DAEMON
 | 
			
		||||
	bool daemon = true;
 | 
			
		||||
#else
 | 
			
		||||
	static constexpr bool daemon = false;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef __linux__
 | 
			
		||||
	bool systemd = false;
 | 
			
		||||
 
 | 
			
		||||
@@ -45,7 +45,10 @@ void
 | 
			
		||||
LogFmt(LogLevel level, const Domain &domain,
 | 
			
		||||
       const S &format_str, Args&&... args) noexcept
 | 
			
		||||
{
 | 
			
		||||
#if FMT_VERSION >= 70000
 | 
			
		||||
#if FMT_VERSION >= 90000
 | 
			
		||||
	return LogVFmt(level, domain, format_str,
 | 
			
		||||
		       fmt::make_format_args(args...));
 | 
			
		||||
#elif FMT_VERSION >= 70000
 | 
			
		||||
	return LogVFmt(level, domain, fmt::to_string_view(format_str),
 | 
			
		||||
		       fmt::make_args_checked<Args...>(format_str,
 | 
			
		||||
						       args...));
 | 
			
		||||
 
 | 
			
		||||
@@ -158,12 +158,15 @@ log_init(const ConfigData &config, bool verbose, bool use_stdout)
 | 
			
		||||
			    getenv("NOTIFY_SOCKET") != nullptr) {
 | 
			
		||||
				/* if MPD was started as a systemd
 | 
			
		||||
				   service, default to journal (which
 | 
			
		||||
				   is connected to fd=2) */
 | 
			
		||||
				   is connected to stdout&stderr) */
 | 
			
		||||
				out_fd = STDOUT_FILENO;
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
#endif
 | 
			
		||||
#ifndef HAVE_SYSLOG
 | 
			
		||||
#ifdef _WIN32
 | 
			
		||||
			/* default to stdout on Windows */
 | 
			
		||||
			out_fd = STDOUT_FILENO;
 | 
			
		||||
#elif !defined(HAVE_SYSLOG)
 | 
			
		||||
			throw std::runtime_error("config parameter 'log_file' not found");
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef HAVE_SYSLOG
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										53
									
								
								src/Main.cxx
									
									
									
									
									
								
							
							
						
						
									
										53
									
								
								src/Main.cxx
									
									
									
									
									
								
							@@ -482,7 +482,10 @@ MainConfigured(const CommandLineOptions &options,
 | 
			
		||||
#ifndef ANDROID
 | 
			
		||||
	setup_log_output();
 | 
			
		||||
 | 
			
		||||
	const ScopeSignalHandlersInit signal_handlers_init(instance);
 | 
			
		||||
	const ScopeSignalHandlersInit signal_handlers_init{
 | 
			
		||||
		instance,
 | 
			
		||||
		options.daemon,
 | 
			
		||||
	};
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	instance.io_thread.Start();
 | 
			
		||||
@@ -590,19 +593,46 @@ MainConfigured(const CommandLineOptions &options,
 | 
			
		||||
 | 
			
		||||
#ifdef ANDROID
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Wrapper for ReadConfigFile() which returns false if the file was
 | 
			
		||||
 * not found.
 | 
			
		||||
 */
 | 
			
		||||
static bool
 | 
			
		||||
TryReadConfigFile(ConfigData &config, Path path)
 | 
			
		||||
{
 | 
			
		||||
	if (!FileExists(path))
 | 
			
		||||
		return false;
 | 
			
		||||
 | 
			
		||||
	ReadConfigFile(config, path);
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
AndroidMain()
 | 
			
		||||
LoadConfigFile(JNIEnv *env, ConfigData &config)
 | 
			
		||||
{
 | 
			
		||||
	/* try loading mpd.conf from
 | 
			
		||||
	   "Android/data/org.musicpd/files/mpd.conf" (the app specific
 | 
			
		||||
	   data directory) first */
 | 
			
		||||
	if (const auto dir = context->GetExternalFilesDir(env);
 | 
			
		||||
	    !dir.IsNull() &&
 | 
			
		||||
	    TryReadConfigFile(config, dir / Path::FromFS("mpd.conf")))
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	/* if that fails, attempt to load "mpd.conf" from the root of
 | 
			
		||||
	   the SD card (pre-0.23.9, ceases to work since Android
 | 
			
		||||
	   12) */
 | 
			
		||||
	if (const auto dir = Environment::getExternalStorageDirectory(env);
 | 
			
		||||
	    !dir.IsNull())
 | 
			
		||||
		TryReadConfigFile(config, dir / Path::FromFS("mpd.conf"));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
AndroidMain(JNIEnv *env)
 | 
			
		||||
{
 | 
			
		||||
	CommandLineOptions options;
 | 
			
		||||
	ConfigData raw_config;
 | 
			
		||||
 | 
			
		||||
	const auto sdcard = Environment::getExternalStorageDirectory();
 | 
			
		||||
	if (!sdcard.IsNull()) {
 | 
			
		||||
		const auto config_path =
 | 
			
		||||
			sdcard / Path::FromFS("mpd.conf");
 | 
			
		||||
		if (FileExists(config_path))
 | 
			
		||||
			ReadConfigFile(raw_config, config_path);
 | 
			
		||||
	}
 | 
			
		||||
	LoadConfigFile(env, raw_config);
 | 
			
		||||
 | 
			
		||||
	MainConfigured(options, raw_config);
 | 
			
		||||
}
 | 
			
		||||
@@ -614,9 +644,12 @@ Java_org_musicpd_Bridge_run(JNIEnv *env, jclass, jobject _context, jobject _logL
 | 
			
		||||
	Java::Init(env);
 | 
			
		||||
	Java::Object::Initialise(env);
 | 
			
		||||
	Java::File::Initialise(env);
 | 
			
		||||
 | 
			
		||||
	Environment::Initialise(env);
 | 
			
		||||
	AtScopeExit(env) { Environment::Deinitialise(env); };
 | 
			
		||||
 | 
			
		||||
	Context::Initialise(env);
 | 
			
		||||
 | 
			
		||||
	context = new Context(env, _context);
 | 
			
		||||
	AtScopeExit() { delete context; };
 | 
			
		||||
 | 
			
		||||
@@ -625,7 +658,7 @@ Java_org_musicpd_Bridge_run(JNIEnv *env, jclass, jobject _context, jobject _logL
 | 
			
		||||
	AtScopeExit() { delete logListener; };
 | 
			
		||||
 | 
			
		||||
	try {
 | 
			
		||||
		AndroidMain();
 | 
			
		||||
		AndroidMain(env);
 | 
			
		||||
	} catch (...) {
 | 
			
		||||
		LogError(std::current_exception());
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,6 @@
 | 
			
		||||
#include "Log.hxx"
 | 
			
		||||
#include "lib/fmt/ExceptionFormatter.hxx"
 | 
			
		||||
#include "song/DetachedSong.hxx"
 | 
			
		||||
#include "mixer/Volume.hxx"
 | 
			
		||||
#include "IdleFlags.hxx"
 | 
			
		||||
#include "client/Listener.hxx"
 | 
			
		||||
#include "client/Client.hxx"
 | 
			
		||||
@@ -206,7 +205,7 @@ Partition::OnBorderPause() noexcept
 | 
			
		||||
void
 | 
			
		||||
Partition::OnMixerVolumeChanged(Mixer &, int) noexcept
 | 
			
		||||
{
 | 
			
		||||
	InvalidateHardwareVolume();
 | 
			
		||||
	mixer_memento.InvalidateHardwareVolume();
 | 
			
		||||
 | 
			
		||||
	/* notify clients */
 | 
			
		||||
	EmitIdle(IDLE_MIXER);
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,7 @@
 | 
			
		||||
#include "queue/Listener.hxx"
 | 
			
		||||
#include "output/MultipleOutputs.hxx"
 | 
			
		||||
#include "mixer/Listener.hxx"
 | 
			
		||||
#include "mixer/Memento.hxx"
 | 
			
		||||
#include "player/Control.hxx"
 | 
			
		||||
#include "player/Listener.hxx"
 | 
			
		||||
#include "protocol/RangeArg.hxx"
 | 
			
		||||
@@ -76,6 +77,8 @@ struct Partition final : QueueListener, PlayerListener, MixerListener {
 | 
			
		||||
 | 
			
		||||
	MultipleOutputs outputs;
 | 
			
		||||
 | 
			
		||||
	MixerMemento mixer_memento;
 | 
			
		||||
 | 
			
		||||
	PlayerControl pc;
 | 
			
		||||
 | 
			
		||||
	ReplayGainMode replay_gain_mode = ReplayGainMode::OFF;
 | 
			
		||||
 
 | 
			
		||||
@@ -81,6 +81,9 @@ spl_valid_name(const char *name_utf8)
 | 
			
		||||
	 */
 | 
			
		||||
 | 
			
		||||
	return std::strchr(name_utf8, '/') == nullptr &&
 | 
			
		||||
#ifdef _WIN32
 | 
			
		||||
		std::strchr(name_utf8, '\\') == nullptr &&
 | 
			
		||||
#endif
 | 
			
		||||
		std::strchr(name_utf8, '\n') == nullptr &&
 | 
			
		||||
		std::strchr(name_utf8, '\r') == nullptr;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,11 @@
 | 
			
		||||
#include <boost/intrusive/list.hpp>
 | 
			
		||||
#include <boost/intrusive/unordered_set.hpp>
 | 
			
		||||
 | 
			
		||||
#include <array>
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
class RemoteTagCacheHandler;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,7 @@
 | 
			
		||||
#include "TagPrint.hxx"
 | 
			
		||||
#include "client/Response.hxx"
 | 
			
		||||
#include "fs/Traits.hxx"
 | 
			
		||||
#include "lib/fmt/AudioFormatFormatter.hxx"
 | 
			
		||||
#include "time/ChronoUtil.hxx"
 | 
			
		||||
#include "util/StringBuffer.hxx"
 | 
			
		||||
#include "util/UriUtil.hxx"
 | 
			
		||||
@@ -93,7 +94,7 @@ song_print_info(Response &r, const LightSong &song, bool base) noexcept
 | 
			
		||||
		time_print(r, "Last-Modified", song.mtime);
 | 
			
		||||
 | 
			
		||||
	if (song.audio_format.IsDefined())
 | 
			
		||||
		r.Fmt(FMT_STRING("Format: {}\n"), ToString(song.audio_format));
 | 
			
		||||
		r.Fmt(FMT_STRING("Format: {}\n"), song.audio_format);
 | 
			
		||||
 | 
			
		||||
	tag_print_values(r, song.tag);
 | 
			
		||||
 | 
			
		||||
@@ -116,7 +117,7 @@ song_print_info(Response &r, const DetachedSong &song, bool base) noexcept
 | 
			
		||||
		time_print(r, "Last-Modified", song.GetLastModified());
 | 
			
		||||
 | 
			
		||||
	if (const auto &f = song.GetAudioFormat(); f.IsDefined())
 | 
			
		||||
		r.Fmt(FMT_STRING("Format: {}\n"), ToString(f));
 | 
			
		||||
		r.Fmt(FMT_STRING("Format: {}\n"), f);
 | 
			
		||||
 | 
			
		||||
	tag_print_values(r, song.GetTag());
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -63,6 +63,9 @@ song_save(BufferedOutputStream &os, const Song &song)
 | 
			
		||||
	if (song.audio_format.IsDefined())
 | 
			
		||||
		os.Format("Format: %s\n", ToString(song.audio_format).c_str());
 | 
			
		||||
 | 
			
		||||
	if (song.in_playlist)
 | 
			
		||||
		os.Write("InPlaylist: yes\n");
 | 
			
		||||
 | 
			
		||||
	if (!IsNegative(song.mtime))
 | 
			
		||||
		os.Format(SONG_MTIME ": %li\n",
 | 
			
		||||
			  (long)std::chrono::system_clock::to_time_t(song.mtime));
 | 
			
		||||
@@ -86,7 +89,7 @@ song_save(BufferedOutputStream &os, const DetachedSong &song)
 | 
			
		||||
 | 
			
		||||
DetachedSong
 | 
			
		||||
song_load(LineReader &file, const char *uri,
 | 
			
		||||
	  std::string *target_r)
 | 
			
		||||
	  std::string *target_r, bool *in_playlist_r)
 | 
			
		||||
{
 | 
			
		||||
	DetachedSong song(uri);
 | 
			
		||||
 | 
			
		||||
@@ -132,6 +135,9 @@ song_load(LineReader &file, const char *uri,
 | 
			
		||||
 | 
			
		||||
			song.SetStartTime(SongTime::FromMS(start_ms));
 | 
			
		||||
			song.SetEndTime(SongTime::FromMS(end_ms));
 | 
			
		||||
		} else if (StringIsEqual(line, "InPlaylist")) {
 | 
			
		||||
			if (in_playlist_r != nullptr)
 | 
			
		||||
				*in_playlist_r = StringIsEqual(value, "yes");
 | 
			
		||||
		} else {
 | 
			
		||||
			throw FormatRuntimeError("unknown line in db: %s", line);
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -44,6 +44,6 @@ song_save(BufferedOutputStream &os, const DetachedSong &song);
 | 
			
		||||
 */
 | 
			
		||||
DetachedSong
 | 
			
		||||
song_load(LineReader &file, const char *uri,
 | 
			
		||||
	  std::string *target_r=nullptr);
 | 
			
		||||
	  std::string *target_r=nullptr, bool *in_playlist_r=nullptr);
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,6 @@
 | 
			
		||||
#include "storage/StorageState.hxx"
 | 
			
		||||
#include "Partition.hxx"
 | 
			
		||||
#include "Instance.hxx"
 | 
			
		||||
#include "mixer/Volume.hxx"
 | 
			
		||||
#include "SongLoader.hxx"
 | 
			
		||||
#include "util/Domain.hxx"
 | 
			
		||||
#include "Log.hxx"
 | 
			
		||||
@@ -47,7 +46,7 @@ StateFile::StateFile(StateFileConfig &&_config,
 | 
			
		||||
void
 | 
			
		||||
StateFile::RememberVersions() noexcept
 | 
			
		||||
{
 | 
			
		||||
	prev_volume_version = sw_volume_state_get_hash();
 | 
			
		||||
	prev_volume_version = partition.mixer_memento.GetSoftwareVolumeStateHash();
 | 
			
		||||
	prev_output_version = audio_output_state_get_version();
 | 
			
		||||
	prev_playlist_version = playlist_state_get_hash(partition.playlist,
 | 
			
		||||
							partition.pc);
 | 
			
		||||
@@ -59,7 +58,7 @@ StateFile::RememberVersions() noexcept
 | 
			
		||||
bool
 | 
			
		||||
StateFile::IsModified() const noexcept
 | 
			
		||||
{
 | 
			
		||||
	return prev_volume_version != sw_volume_state_get_hash() ||
 | 
			
		||||
	return prev_volume_version != partition.mixer_memento.GetSoftwareVolumeStateHash() ||
 | 
			
		||||
		prev_output_version != audio_output_state_get_version() ||
 | 
			
		||||
		prev_playlist_version != playlist_state_get_hash(partition.playlist,
 | 
			
		||||
								 partition.pc)
 | 
			
		||||
@@ -72,7 +71,7 @@ StateFile::IsModified() const noexcept
 | 
			
		||||
inline void
 | 
			
		||||
StateFile::Write(BufferedOutputStream &os)
 | 
			
		||||
{
 | 
			
		||||
	save_sw_volume_state(os);
 | 
			
		||||
	partition.mixer_memento.SaveSoftwareVolumeState(os);
 | 
			
		||||
	audio_output_state_save(os, partition.outputs);
 | 
			
		||||
 | 
			
		||||
#ifdef ENABLE_DATABASE
 | 
			
		||||
@@ -125,7 +124,7 @@ try {
 | 
			
		||||
 | 
			
		||||
	const char *line;
 | 
			
		||||
	while ((line = file.ReadLine()) != nullptr) {
 | 
			
		||||
		success = read_sw_volume_state(line, partition.outputs) ||
 | 
			
		||||
		success = partition.mixer_memento.LoadSoftwareVolumeState(line, partition.outputs) ||
 | 
			
		||||
			audio_output_state_read(line, partition.outputs) ||
 | 
			
		||||
			playlist_state_restore(config, line, file, song_loader,
 | 
			
		||||
					       partition.playlist,
 | 
			
		||||
 
 | 
			
		||||
@@ -32,7 +32,7 @@ StateFileConfig::StateFileConfig(const ConfigData &config)
 | 
			
		||||
{
 | 
			
		||||
#ifdef ANDROID
 | 
			
		||||
	if (path.IsNull()) {
 | 
			
		||||
		const auto cache_dir = GetUserCacheDir();
 | 
			
		||||
		const auto cache_dir = GetAppCacheDir();
 | 
			
		||||
		if (cache_dir.IsNull())
 | 
			
		||||
			return;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -35,8 +35,9 @@ tag_print_types(Response &r) noexcept
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
tag_print(Response &r, TagType type, StringView value) noexcept
 | 
			
		||||
tag_print(Response &r, TagType type, StringView _value) noexcept
 | 
			
		||||
{
 | 
			
		||||
	const std::string_view value{_value};
 | 
			
		||||
	r.Fmt(FMT_STRING("{}: {}\n"), tag_item_names[type], value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -36,5 +36,5 @@ time_print(Response &r, const char *name,
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r.Fmt(FMT_STRING("{}: {}\n"), name, s);
 | 
			
		||||
	r.Fmt(FMT_STRING("{}: {}\n"), name, s.c_str());
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -26,19 +26,30 @@
 | 
			
		||||
 | 
			
		||||
#include "AudioManager.hxx"
 | 
			
		||||
 | 
			
		||||
AllocatedPath
 | 
			
		||||
Context::GetExternalFilesDir(JNIEnv *env, const char *_type) noexcept
 | 
			
		||||
static jmethodID getExternalFilesDir_method,
 | 
			
		||||
  getCacheDir_method,
 | 
			
		||||
  getSystemService_method;
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
Context::Initialise(JNIEnv *env) noexcept
 | 
			
		||||
{
 | 
			
		||||
	assert(_type != nullptr);
 | 
			
		||||
	Java::Class cls{env, "android/content/Context"};
 | 
			
		||||
 | 
			
		||||
	Java::Class cls{env, env->GetObjectClass(Get())};
 | 
			
		||||
	jmethodID method = env->GetMethodID(cls, "getExternalFilesDir",
 | 
			
		||||
					    "(Ljava/lang/String;)Ljava/io/File;");
 | 
			
		||||
	assert(method);
 | 
			
		||||
	getExternalFilesDir_method = env->GetMethodID(cls, "getExternalFilesDir",
 | 
			
		||||
						      "(Ljava/lang/String;)Ljava/io/File;");
 | 
			
		||||
	getCacheDir_method = env->GetMethodID(cls, "getCacheDir",
 | 
			
		||||
					      "()Ljava/io/File;");
 | 
			
		||||
	getSystemService_method = env->GetMethodID(cls, "getSystemService",
 | 
			
		||||
						   "(Ljava/lang/String;)Ljava/lang/Object;");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	Java::String type{env, _type};
 | 
			
		||||
AllocatedPath
 | 
			
		||||
Context::GetExternalFilesDir(JNIEnv *env, const char *type) noexcept
 | 
			
		||||
{
 | 
			
		||||
	assert(type != nullptr);
 | 
			
		||||
 | 
			
		||||
	jobject file = env->CallObjectMethod(Get(), method, type.Get());
 | 
			
		||||
	jobject file = env->CallObjectMethod(Get(), getExternalFilesDir_method,
 | 
			
		||||
					     Java::String::Optional(env, type).Get());
 | 
			
		||||
	if (Java::DiscardException(env) || file == nullptr)
 | 
			
		||||
		return nullptr;
 | 
			
		||||
 | 
			
		||||
@@ -50,12 +61,7 @@ Context::GetCacheDir(JNIEnv *env) const noexcept
 | 
			
		||||
{
 | 
			
		||||
	assert(env != nullptr);
 | 
			
		||||
 | 
			
		||||
	Java::Class cls(env, env->GetObjectClass(Get()));
 | 
			
		||||
	jmethodID method = env->GetMethodID(cls, "getCacheDir",
 | 
			
		||||
					    "()Ljava/io/File;");
 | 
			
		||||
	assert(method);
 | 
			
		||||
 | 
			
		||||
	jobject file = env->CallObjectMethod(Get(), method);
 | 
			
		||||
	jobject file = env->CallObjectMethod(Get(), getCacheDir_method);
 | 
			
		||||
	if (Java::DiscardException(env) || file == nullptr)
 | 
			
		||||
		return nullptr;
 | 
			
		||||
 | 
			
		||||
@@ -67,13 +73,8 @@ Context::GetAudioManager(JNIEnv *env) noexcept
 | 
			
		||||
{
 | 
			
		||||
	assert(env != nullptr);
 | 
			
		||||
 | 
			
		||||
	Java::Class cls(env, env->GetObjectClass(Get()));
 | 
			
		||||
	jmethodID method = env->GetMethodID(cls, "getSystemService",
 | 
			
		||||
					    "(Ljava/lang/String;)Ljava/lang/Object;");
 | 
			
		||||
	assert(method);
 | 
			
		||||
 | 
			
		||||
	Java::String name(env, "audio");
 | 
			
		||||
	jobject am = env->CallObjectMethod(Get(), method, name.Get());
 | 
			
		||||
	jobject am = env->CallObjectMethod(Get(), getSystemService_method, name.Get());
 | 
			
		||||
	if (Java::DiscardException(env) || am == nullptr)
 | 
			
		||||
		return nullptr;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -27,12 +27,21 @@ class AudioManager;
 | 
			
		||||
 | 
			
		||||
class Context : public Java::GlobalObject {
 | 
			
		||||
public:
 | 
			
		||||
	/**
 | 
			
		||||
	 * Global initialisation.  Looks up the methods of the
 | 
			
		||||
	 * Context Java class.
 | 
			
		||||
	 */
 | 
			
		||||
	static void Initialise(JNIEnv *env) noexcept;
 | 
			
		||||
 | 
			
		||||
	Context(JNIEnv *env, jobject obj) noexcept
 | 
			
		||||
		:Java::GlobalObject(env, obj) {}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * @param type the subdirectory name; may be nullptr
 | 
			
		||||
	 */
 | 
			
		||||
	[[gnu::pure]]
 | 
			
		||||
	AllocatedPath GetExternalFilesDir(JNIEnv *env,
 | 
			
		||||
					  const char *type) noexcept;
 | 
			
		||||
					  const char *type=nullptr) noexcept;
 | 
			
		||||
 | 
			
		||||
	[[gnu::pure]]
 | 
			
		||||
	AllocatedPath GetCacheDir(JNIEnv *env) const noexcept;
 | 
			
		||||
 
 | 
			
		||||
@@ -25,13 +25,13 @@
 | 
			
		||||
#include "fs/AllocatedPath.hxx"
 | 
			
		||||
 | 
			
		||||
namespace Environment {
 | 
			
		||||
	static Java::TrivialClass cls;
 | 
			
		||||
	static jmethodID getExternalStorageDirectory_method;
 | 
			
		||||
	static jmethodID getExternalStoragePublicDirectory_method;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static Java::TrivialClass cls;
 | 
			
		||||
static jmethodID getExternalStorageDirectory_method;
 | 
			
		||||
static jmethodID getExternalStoragePublicDirectory_method;
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
Environment::Initialise(JNIEnv *env) noexcept
 | 
			
		||||
Initialise(JNIEnv *env) noexcept
 | 
			
		||||
{
 | 
			
		||||
	cls.Find(env, "android/os/Environment");
 | 
			
		||||
 | 
			
		||||
@@ -45,16 +45,14 @@ Environment::Initialise(JNIEnv *env) noexcept
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
Environment::Deinitialise(JNIEnv *env) noexcept
 | 
			
		||||
Deinitialise(JNIEnv *env) noexcept
 | 
			
		||||
{
 | 
			
		||||
	cls.Clear(env);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AllocatedPath
 | 
			
		||||
Environment::getExternalStorageDirectory() noexcept
 | 
			
		||||
getExternalStorageDirectory(JNIEnv *env) noexcept
 | 
			
		||||
{
 | 
			
		||||
	JNIEnv *env = Java::GetEnv();
 | 
			
		||||
 | 
			
		||||
	jobject file =
 | 
			
		||||
		env->CallStaticObjectMethod(cls,
 | 
			
		||||
					    getExternalStorageDirectory_method);
 | 
			
		||||
@@ -65,20 +63,20 @@ Environment::getExternalStorageDirectory() noexcept
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AllocatedPath
 | 
			
		||||
Environment::getExternalStoragePublicDirectory(const char *type) noexcept
 | 
			
		||||
getExternalStoragePublicDirectory(JNIEnv *env, const char *type) noexcept
 | 
			
		||||
{
 | 
			
		||||
	if (getExternalStoragePublicDirectory_method == nullptr)
 | 
			
		||||
		/* needs API level 8 */
 | 
			
		||||
		return nullptr;
 | 
			
		||||
 | 
			
		||||
	JNIEnv *env = Java::GetEnv();
 | 
			
		||||
 | 
			
		||||
	Java::String type2(env, type);
 | 
			
		||||
	jobject file = env->CallStaticObjectMethod(Environment::cls,
 | 
			
		||||
						   Environment::getExternalStoragePublicDirectory_method,
 | 
			
		||||
	jobject file = env->CallStaticObjectMethod(cls,
 | 
			
		||||
						   getExternalStoragePublicDirectory_method,
 | 
			
		||||
						   type2.Get());
 | 
			
		||||
	if (file == nullptr)
 | 
			
		||||
		return nullptr;
 | 
			
		||||
 | 
			
		||||
	return Java::File::ToAbsolutePath(env, file);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace Environment
 | 
			
		||||
 
 | 
			
		||||
@@ -17,27 +17,29 @@
 | 
			
		||||
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef MPD_ANDROID_ENVIRONMENT_HXX
 | 
			
		||||
#define MPD_ANDROID_ENVIRONMENT_HXX
 | 
			
		||||
 | 
			
		||||
#include "util/Compiler.h"
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <jni.h>
 | 
			
		||||
 | 
			
		||||
class AllocatedPath;
 | 
			
		||||
 | 
			
		||||
namespace Environment {
 | 
			
		||||
	void Initialise(JNIEnv *env) noexcept;
 | 
			
		||||
	void Deinitialise(JNIEnv *env) noexcept;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Determine the mount point of the external SD card.
 | 
			
		||||
	 */
 | 
			
		||||
	[[gnu::pure]]
 | 
			
		||||
	AllocatedPath getExternalStorageDirectory() noexcept;
 | 
			
		||||
void
 | 
			
		||||
Initialise(JNIEnv *env) noexcept;
 | 
			
		||||
 | 
			
		||||
	[[gnu::pure]]
 | 
			
		||||
	AllocatedPath getExternalStoragePublicDirectory(const char *type) noexcept;
 | 
			
		||||
}
 | 
			
		||||
void
 | 
			
		||||
Deinitialise(JNIEnv *env) noexcept;
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
/**
 | 
			
		||||
 * Determine the mount point of the external SD card.
 | 
			
		||||
 */
 | 
			
		||||
[[gnu::pure]]
 | 
			
		||||
AllocatedPath
 | 
			
		||||
getExternalStorageDirectory(JNIEnv *env) noexcept;
 | 
			
		||||
 | 
			
		||||
[[gnu::pure]]
 | 
			
		||||
AllocatedPath
 | 
			
		||||
getExternalStoragePublicDirectory(JNIEnv *env, const char *type) noexcept;
 | 
			
		||||
 | 
			
		||||
} // namespace Environment
 | 
			
		||||
 
 | 
			
		||||
@@ -31,6 +31,7 @@
 | 
			
		||||
#include "ErrorRef.hxx"
 | 
			
		||||
#include "StringRef.hxx"
 | 
			
		||||
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <stdexcept>
 | 
			
		||||
 | 
			
		||||
namespace Apple {
 | 
			
		||||
@@ -57,8 +58,8 @@ ThrowOSStatus(OSStatus status, const char *_msg)
 | 
			
		||||
	const Apple::StringRef cfstr(cferr.CopyDescription());
 | 
			
		||||
 | 
			
		||||
	char msg[1024];
 | 
			
		||||
	strcpy(msg, _msg);
 | 
			
		||||
	size_t length = strlen(msg);
 | 
			
		||||
	std::strcpy(msg, _msg);
 | 
			
		||||
	size_t length = std::strlen(msg);
 | 
			
		||||
 | 
			
		||||
	cfstr.GetCString(msg + length, sizeof(msg) - length);
 | 
			
		||||
	throw std::runtime_error(msg);
 | 
			
		||||
 
 | 
			
		||||
@@ -166,7 +166,7 @@ class Iso9660InputStream final : public InputStream {
 | 
			
		||||
			assert(fill <= data.size());
 | 
			
		||||
			assert(position <= fill);
 | 
			
		||||
 | 
			
		||||
			return {&data[position], &data[fill]};
 | 
			
		||||
			return {data.data() + position, data.data() + fill};
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		void Consume(size_t nbytes) noexcept {
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,10 @@ if libzzip_dep.found()
 | 
			
		||||
  found_archive_plugin = true
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
if not found_archive_plugin
 | 
			
		||||
  subdir_done()
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
archive_plugins = static_library(
 | 
			
		||||
  'archive_plugins',
 | 
			
		||||
  archive_plugins_sources,
 | 
			
		||||
 
 | 
			
		||||
@@ -82,7 +82,10 @@ public:
 | 
			
		||||
 | 
			
		||||
	template<typename S, typename... Args>
 | 
			
		||||
	bool Fmt(const S &format_str, Args&&... args) noexcept {
 | 
			
		||||
#if FMT_VERSION >= 70000
 | 
			
		||||
#if FMT_VERSION >= 90000
 | 
			
		||||
		return VFmt(format_str,
 | 
			
		||||
			    fmt::make_format_args(args...));
 | 
			
		||||
#elif FMT_VERSION >= 70000
 | 
			
		||||
		return VFmt(fmt::to_string_view(format_str),
 | 
			
		||||
			    fmt::make_args_checked<Args...>(format_str,
 | 
			
		||||
							    args...));
 | 
			
		||||
@@ -109,7 +112,10 @@ public:
 | 
			
		||||
	template<typename S, typename... Args>
 | 
			
		||||
	void FmtError(enum ack code,
 | 
			
		||||
		      const S &format_str, Args&&... args) noexcept {
 | 
			
		||||
#if FMT_VERSION >= 70000
 | 
			
		||||
#if FMT_VERSION >= 90000
 | 
			
		||||
		return VFmtError(code, format_str,
 | 
			
		||||
				 fmt::make_format_args(args...));
 | 
			
		||||
#elif FMT_VERSION >= 70000
 | 
			
		||||
		return VFmtError(code, fmt::to_string_view(format_str),
 | 
			
		||||
				 fmt::make_args_checked<Args...>(format_str,
 | 
			
		||||
								 args...));
 | 
			
		||||
 
 | 
			
		||||
@@ -41,6 +41,8 @@
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#include <limits.h> // for UINT_MAX
 | 
			
		||||
 | 
			
		||||
CommandResult
 | 
			
		||||
handle_listfiles_db(Client &client, Response &r, const char *uri)
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -100,10 +100,6 @@ handle_listfiles_local(Response &r, Path path_fs)
 | 
			
		||||
	return CommandResult::OK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#if defined(_WIN32) && GCC_CHECK_VERSION(4,6)
 | 
			
		||||
#pragma GCC diagnostic pop
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
gcc_pure
 | 
			
		||||
static bool
 | 
			
		||||
IsValidName(const StringView s) noexcept
 | 
			
		||||
@@ -130,7 +126,8 @@ public:
 | 
			
		||||
	explicit PrintCommentHandler(Response &_response) noexcept
 | 
			
		||||
		:NullTagHandler(WANT_PAIR), response(_response) {}
 | 
			
		||||
 | 
			
		||||
	void OnPair(StringView key, StringView value) noexcept override {
 | 
			
		||||
	void OnPair(StringView _key, StringView _value) noexcept override {
 | 
			
		||||
		const std::string_view key{_key}, value{_value};
 | 
			
		||||
		if (IsValidName(key) && IsValidValue(value))
 | 
			
		||||
			response.Fmt(FMT_STRING("{}: {}\n"), key, value);
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,6 @@
 | 
			
		||||
#include "TimePrint.hxx"
 | 
			
		||||
#include "decoder/DecoderPrint.hxx"
 | 
			
		||||
#include "ls.hxx"
 | 
			
		||||
#include "mixer/Volume.hxx"
 | 
			
		||||
#include "time/ChronoUtil.hxx"
 | 
			
		||||
#include "util/UriUtil.hxx"
 | 
			
		||||
#include "util/StringAPI.hxx"
 | 
			
		||||
@@ -325,7 +324,7 @@ handle_getvol(Client &client, Request, Response &r)
 | 
			
		||||
{
 | 
			
		||||
	auto &partition = client.GetPartition();
 | 
			
		||||
 | 
			
		||||
	const auto volume = volume_level_get(partition.outputs);
 | 
			
		||||
	const auto volume = partition.mixer_memento.GetVolume(partition.outputs);
 | 
			
		||||
	if (volume >= 0)
 | 
			
		||||
		r.Fmt(FMT_STRING("volume: {}\n"), volume);
 | 
			
		||||
 | 
			
		||||
@@ -333,15 +332,13 @@ handle_getvol(Client &client, Request, Response &r)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CommandResult
 | 
			
		||||
handle_setvol(Client &client, Request args, Response &r)
 | 
			
		||||
handle_setvol(Client &client, Request args, Response &)
 | 
			
		||||
{
 | 
			
		||||
	unsigned level = args.ParseUnsigned(0, 100);
 | 
			
		||||
 | 
			
		||||
	if (!volume_level_change(client.GetPartition().outputs, level)) {
 | 
			
		||||
		r.Error(ACK_ERROR_SYSTEM, "problems setting volume");
 | 
			
		||||
		return CommandResult::ERROR;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	auto &partition = client.GetPartition();
 | 
			
		||||
	partition.mixer_memento.SetVolume(partition.outputs, level);
 | 
			
		||||
	partition.EmitIdle(IDLE_MIXER);
 | 
			
		||||
	return CommandResult::OK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -350,9 +347,11 @@ handle_volume(Client &client, Request args, Response &r)
 | 
			
		||||
{
 | 
			
		||||
	int relative = args.ParseInt(0, -100, 100);
 | 
			
		||||
 | 
			
		||||
	auto &outputs = client.GetPartition().outputs;
 | 
			
		||||
	auto &partition = client.GetPartition();
 | 
			
		||||
	auto &outputs = partition.outputs;
 | 
			
		||||
	auto &mixer_memento = partition.mixer_memento;
 | 
			
		||||
 | 
			
		||||
	const int old_volume = volume_level_get(outputs);
 | 
			
		||||
	const int old_volume = mixer_memento.GetVolume(outputs);
 | 
			
		||||
	if (old_volume < 0) {
 | 
			
		||||
		r.Error(ACK_ERROR_SYSTEM, "No mixer");
 | 
			
		||||
		return CommandResult::ERROR;
 | 
			
		||||
@@ -364,10 +363,9 @@ handle_volume(Client &client, Request args, Response &r)
 | 
			
		||||
	else if (new_volume > 100)
 | 
			
		||||
		new_volume = 100;
 | 
			
		||||
 | 
			
		||||
	if (new_volume != old_volume &&
 | 
			
		||||
	    !volume_level_change(outputs, new_volume)) {
 | 
			
		||||
		r.Error(ACK_ERROR_SYSTEM, "problems setting volume");
 | 
			
		||||
		return CommandResult::ERROR;
 | 
			
		||||
	if (new_volume != old_volume) {
 | 
			
		||||
		mixer_memento.SetVolume(outputs, new_volume);
 | 
			
		||||
		partition.EmitIdle(IDLE_MIXER);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return CommandResult::OK;
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,11 @@ handle_enableoutput(Client &client, Request args, Response &r)
 | 
			
		||||
	assert(args.size == 1);
 | 
			
		||||
	unsigned device = args.ParseUnsigned(0);
 | 
			
		||||
 | 
			
		||||
	if (!audio_output_enable_index(client.GetPartition().outputs, device)) {
 | 
			
		||||
	auto &partition = client.GetPartition();
 | 
			
		||||
 | 
			
		||||
	if (!audio_output_enable_index(partition.outputs,
 | 
			
		||||
				       partition.mixer_memento,
 | 
			
		||||
				       device)) {
 | 
			
		||||
		r.Error(ACK_ERROR_NO_EXIST, "No such audio output");
 | 
			
		||||
		return CommandResult::ERROR;
 | 
			
		||||
	}
 | 
			
		||||
@@ -47,7 +51,11 @@ handle_disableoutput(Client &client, Request args, Response &r)
 | 
			
		||||
	assert(args.size == 1);
 | 
			
		||||
	unsigned device = args.ParseUnsigned(0);
 | 
			
		||||
 | 
			
		||||
	if (!audio_output_disable_index(client.GetPartition().outputs, device)) {
 | 
			
		||||
	auto &partition = client.GetPartition();
 | 
			
		||||
 | 
			
		||||
	if (!audio_output_disable_index(partition.outputs,
 | 
			
		||||
					partition.mixer_memento,
 | 
			
		||||
					device)) {
 | 
			
		||||
		r.Error(ACK_ERROR_NO_EXIST, "No such audio output");
 | 
			
		||||
		return CommandResult::ERROR;
 | 
			
		||||
	}
 | 
			
		||||
@@ -61,7 +69,11 @@ handle_toggleoutput(Client &client, Request args, Response &r)
 | 
			
		||||
	assert(args.size == 1);
 | 
			
		||||
	unsigned device = args.ParseUnsigned(0);
 | 
			
		||||
 | 
			
		||||
	if (!audio_output_toggle_index(client.GetPartition().outputs, device)) {
 | 
			
		||||
	auto &partition = client.GetPartition();
 | 
			
		||||
 | 
			
		||||
	if (!audio_output_toggle_index(partition.outputs,
 | 
			
		||||
					partition.mixer_memento,
 | 
			
		||||
				       device)) {
 | 
			
		||||
		r.Error(ACK_ERROR_NO_EXIST, "No such audio output");
 | 
			
		||||
		return CommandResult::ERROR;
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -25,10 +25,10 @@
 | 
			
		||||
#include "SingleMode.hxx"
 | 
			
		||||
#include "client/Client.hxx"
 | 
			
		||||
#include "client/Response.hxx"
 | 
			
		||||
#include "mixer/Volume.hxx"
 | 
			
		||||
#include "Partition.hxx"
 | 
			
		||||
#include "Instance.hxx"
 | 
			
		||||
#include "IdleFlags.hxx"
 | 
			
		||||
#include "lib/fmt/AudioFormatFormatter.hxx"
 | 
			
		||||
#include "util/StringBuffer.hxx"
 | 
			
		||||
#include "util/ScopeExit.hxx"
 | 
			
		||||
#include "util/Exception.hxx"
 | 
			
		||||
@@ -131,7 +131,7 @@ handle_status(Client &client, [[maybe_unused]] Request args, Response &r)
 | 
			
		||||
 | 
			
		||||
	const auto &playlist = partition.playlist;
 | 
			
		||||
 | 
			
		||||
	const auto volume = volume_level_get(partition.outputs);
 | 
			
		||||
	const auto volume = partition.mixer_memento.GetVolume(partition.outputs);
 | 
			
		||||
	if (volume >= 0)
 | 
			
		||||
		r.Fmt(FMT_STRING("volume: {}\n"), volume);
 | 
			
		||||
 | 
			
		||||
@@ -186,7 +186,7 @@ handle_status(Client &client, [[maybe_unused]] Request args, Response &r)
 | 
			
		||||
 | 
			
		||||
		if (player_status.audio_format.IsDefined())
 | 
			
		||||
			r.Fmt(FMT_STRING(COMMAND_STATUS_AUDIO ": {}\n"),
 | 
			
		||||
			      ToString(player_status.audio_format));
 | 
			
		||||
			      player_status.audio_format);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
#ifdef ENABLE_DATABASE
 | 
			
		||||
 
 | 
			
		||||
@@ -83,10 +83,6 @@ handle_listfiles_storage(Response &r, StorageDirectoryReader &reader)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#if defined(_WIN32) && GCC_CHECK_VERSION(4,6)
 | 
			
		||||
#pragma GCC diagnostic pop
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
CommandResult
 | 
			
		||||
handle_listfiles_storage(Response &r, Storage &storage, const char *uri)
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,7 @@
 | 
			
		||||
#include "config/Param.hxx"
 | 
			
		||||
#include "config/Block.hxx"
 | 
			
		||||
#include "fs/AllocatedPath.hxx"
 | 
			
		||||
#include "fs/FileSystem.hxx"
 | 
			
		||||
#include "fs/StandardDirectory.hxx"
 | 
			
		||||
#include "util/RuntimeError.hxx"
 | 
			
		||||
 | 
			
		||||
@@ -51,17 +52,30 @@ CreateConfiguredDatabase(const ConfigData &config,
 | 
			
		||||
	} else {
 | 
			
		||||
		/* if there is no override, use the cache directory */
 | 
			
		||||
 | 
			
		||||
		const AllocatedPath cache_dir = GetUserCacheDir();
 | 
			
		||||
		const AllocatedPath cache_dir = GetAppCacheDir();
 | 
			
		||||
		if (cache_dir.IsNull())
 | 
			
		||||
			return nullptr;
 | 
			
		||||
 | 
			
		||||
		const auto db_file = cache_dir / Path::FromFS(PATH_LITERAL("mpd.db"));
 | 
			
		||||
		const auto db_file = cache_dir / Path::FromFS(PATH_LITERAL("db"));
 | 
			
		||||
		auto db_file_utf8 = db_file.ToUTF8();
 | 
			
		||||
		if (db_file_utf8.empty())
 | 
			
		||||
			return nullptr;
 | 
			
		||||
 | 
			
		||||
		ConfigBlock block;
 | 
			
		||||
		block.AddBlockParam("path", std::move(db_file_utf8), -1);
 | 
			
		||||
 | 
			
		||||
		{
 | 
			
		||||
			const auto mounts_dir = cache_dir
 | 
			
		||||
				/ Path::FromFS(PATH_LITERAL("mounts"));
 | 
			
		||||
			CreateDirectoryNoThrow(mounts_dir);
 | 
			
		||||
 | 
			
		||||
			if (auto mounts_dir_utf8 = mounts_dir.ToUTF8();
 | 
			
		||||
			    !mounts_dir_utf8.empty())
 | 
			
		||||
				block.AddBlockParam("cache_directory",
 | 
			
		||||
						    std::move(mounts_dir_utf8),
 | 
			
		||||
						    -1);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return DatabaseGlobalInit(main_event_loop, io_event_loop,
 | 
			
		||||
					  listener, block);
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -126,6 +126,18 @@ Directory::LookupTargetSong(std::string_view _target) noexcept
 | 
			
		||||
	return lr.directory->FindSong(lr.rest);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
Directory::ClearInPlaylist() noexcept
 | 
			
		||||
{
 | 
			
		||||
	assert(holding_db_lock());
 | 
			
		||||
 | 
			
		||||
	for (auto &child : children)
 | 
			
		||||
		child.ClearInPlaylist();
 | 
			
		||||
 | 
			
		||||
	for (auto &song : songs)
 | 
			
		||||
		song.in_playlist = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
Directory::PruneEmpty() noexcept
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -287,6 +287,14 @@ public:
 | 
			
		||||
	 */
 | 
			
		||||
	SongPtr RemoveSong(Song *song) noexcept;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Recursively walk through the whole tree and set all
 | 
			
		||||
	 * `Song::in_playlist` fields to `false`.
 | 
			
		||||
	 *
 | 
			
		||||
	 * Caller must lock the #db_mutex.
 | 
			
		||||
	 */
 | 
			
		||||
	void ClearInPlaylist() noexcept;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Caller must lock the #db_mutex.
 | 
			
		||||
	 */
 | 
			
		||||
 
 | 
			
		||||
@@ -168,12 +168,14 @@ directory_load(LineReader &file, Directory &directory)
 | 
			
		||||
				throw FormatRuntimeError("Duplicate song '%s'", name);
 | 
			
		||||
 | 
			
		||||
			std::string target;
 | 
			
		||||
			bool in_playlist = false;
 | 
			
		||||
			auto detached_song = song_load(file, name,
 | 
			
		||||
						       &target);
 | 
			
		||||
						       &target, &in_playlist);
 | 
			
		||||
 | 
			
		||||
			auto song = std::make_unique<Song>(std::move(detached_song),
 | 
			
		||||
							   directory);
 | 
			
		||||
			song->target = std::move(target);
 | 
			
		||||
			song->in_playlist = in_playlist;
 | 
			
		||||
 | 
			
		||||
			directory.AddSong(std::move(song));
 | 
			
		||||
		} else if ((p = StringAfterPrefix(line, PLAYLIST_META_BEGIN))) {
 | 
			
		||||
 
 | 
			
		||||
@@ -51,6 +51,15 @@ LockFindSong(Directory &directory, std::string_view name) noexcept
 | 
			
		||||
	return directory.FindSong(name);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[gnu::pure]]
 | 
			
		||||
static bool
 | 
			
		||||
IsAcceptableFilename(std::string_view name) noexcept
 | 
			
		||||
{
 | 
			
		||||
	return !name.empty() &&
 | 
			
		||||
		/* newlines cannot be represented in MPD's protocol */
 | 
			
		||||
		name.find('\n') == name.npos;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
UpdateWalk::UpdateArchiveTree(ArchiveFile &archive, Directory &directory,
 | 
			
		||||
			      const char *name) noexcept
 | 
			
		||||
@@ -58,6 +67,9 @@ UpdateWalk::UpdateArchiveTree(ArchiveFile &archive, Directory &directory,
 | 
			
		||||
	const char *tmp = std::strchr(name, '/');
 | 
			
		||||
	if (tmp) {
 | 
			
		||||
		const std::string_view child_name(name, tmp - name);
 | 
			
		||||
		if (!IsAcceptableFilename(child_name))
 | 
			
		||||
			return;
 | 
			
		||||
 | 
			
		||||
		//add dir is not there already
 | 
			
		||||
		Directory *subdir = LockMakeChild(directory, child_name);
 | 
			
		||||
		subdir->device = DEVICE_INARCHIVE;
 | 
			
		||||
@@ -65,11 +77,8 @@ UpdateWalk::UpdateArchiveTree(ArchiveFile &archive, Directory &directory,
 | 
			
		||||
		//create directories first
 | 
			
		||||
		UpdateArchiveTree(archive, *subdir, tmp + 1);
 | 
			
		||||
	} else {
 | 
			
		||||
		if (StringIsEmpty(name)) {
 | 
			
		||||
			LogWarning(update_domain,
 | 
			
		||||
				   "archive returned directory only");
 | 
			
		||||
		if (!IsAcceptableFilename(name))
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		//add file
 | 
			
		||||
		Song *song = LockFindSong(directory, name);
 | 
			
		||||
 
 | 
			
		||||
@@ -531,6 +531,7 @@ UpdateWalk::Walk(Directory &root, const char *path, bool discard) noexcept
 | 
			
		||||
 | 
			
		||||
	{
 | 
			
		||||
		const ScopeDatabaseLock protect;
 | 
			
		||||
		root.ClearInPlaylist();
 | 
			
		||||
		PurgeDanglingFromPlaylists(root);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -257,6 +257,12 @@ public:
 | 
			
		||||
		return HasFailed();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	[[gnu::pure]]
 | 
			
		||||
	bool LockIsReplayGainEnabled() const noexcept {
 | 
			
		||||
		const std::scoped_lock<Mutex> protect(mutex);
 | 
			
		||||
		return replay_gain_mode != ReplayGainMode::OFF;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Transition this obejct from DecoderState::START to
 | 
			
		||||
	 * DecoderState::DECODE.
 | 
			
		||||
 
 | 
			
		||||
@@ -114,11 +114,11 @@ constexpr const struct DecoderPlugin *decoder_plugins[] = {
 | 
			
		||||
#ifdef ENABLE_ADPLUG
 | 
			
		||||
	&adplug_decoder_plugin,
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef ENABLE_FFMPEG
 | 
			
		||||
	&ffmpeg_decoder_plugin,
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef ENABLE_GME
 | 
			
		||||
	&gme_decoder_plugin,
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef ENABLE_FFMPEG
 | 
			
		||||
	&ffmpeg_decoder_plugin,
 | 
			
		||||
#endif
 | 
			
		||||
	&pcm_decoder_plugin,
 | 
			
		||||
	nullptr
 | 
			
		||||
 
 | 
			
		||||
@@ -36,6 +36,7 @@
 | 
			
		||||
#include "util/RuntimeError.hxx"
 | 
			
		||||
#include "util/Domain.hxx"
 | 
			
		||||
#include "util/ScopeExit.hxx"
 | 
			
		||||
#include "util/StringCompare.hxx"
 | 
			
		||||
#include "thread/Name.hxx"
 | 
			
		||||
#include "tag/ApeReplayGain.hxx"
 | 
			
		||||
#include "Log.hxx"
 | 
			
		||||
@@ -261,12 +262,16 @@ LoadReplayGain(DecoderClient &client, InputStream &is)
 | 
			
		||||
static void
 | 
			
		||||
MaybeLoadReplayGain(DecoderBridge &bridge, InputStream &is)
 | 
			
		||||
{
 | 
			
		||||
	{
 | 
			
		||||
		const std::scoped_lock<Mutex> protect(bridge.dc.mutex);
 | 
			
		||||
		if (bridge.dc.replay_gain_mode == ReplayGainMode::OFF)
 | 
			
		||||
			/* ReplayGain is disabled */
 | 
			
		||||
			return;
 | 
			
		||||
	}
 | 
			
		||||
	if (!bridge.dc.LockIsReplayGainEnabled())
 | 
			
		||||
		/* ReplayGain is disabled */
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	if (is.HasMimeType() &&
 | 
			
		||||
	    StringStartsWith(is.GetMimeType(), "audio/x-mpd-"))
 | 
			
		||||
		/* skip for (virtual) files (e.g. from the
 | 
			
		||||
		   cdio_paranoia input plugin) which cannot possibly
 | 
			
		||||
		   contain tags */
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	LoadReplayGain(bridge, is);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -31,6 +31,7 @@
 | 
			
		||||
#include "lib/ffmpeg/Format.hxx"
 | 
			
		||||
#include "lib/ffmpeg/Codec.hxx"
 | 
			
		||||
#include "lib/ffmpeg/SampleFormat.hxx"
 | 
			
		||||
#include "lib/ffmpeg/LibFmt.hxx"
 | 
			
		||||
#include "../DecoderAPI.hxx"
 | 
			
		||||
#include "FfmpegMetaData.hxx"
 | 
			
		||||
#include "FfmpegIo.hxx"
 | 
			
		||||
@@ -384,7 +385,8 @@ static void
 | 
			
		||||
FfmpegParseMetaData(const AVStream &stream,
 | 
			
		||||
		    ReplayGainInfo &rg, MixRampInfo &mr)
 | 
			
		||||
{
 | 
			
		||||
	FfmpegParseMetaData(*stream.metadata, rg, mr);
 | 
			
		||||
	if (stream.metadata != nullptr)
 | 
			
		||||
		FfmpegParseMetaData(*stream.metadata, rg, mr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
@@ -393,7 +395,9 @@ FfmpegParseMetaData(const AVFormatContext &format_context, int audio_stream,
 | 
			
		||||
{
 | 
			
		||||
	assert(audio_stream >= 0);
 | 
			
		||||
 | 
			
		||||
	FfmpegParseMetaData(*format_context.metadata, rg, mr);
 | 
			
		||||
	if (format_context.metadata != nullptr)
 | 
			
		||||
		FfmpegParseMetaData(*format_context.metadata, rg, mr);
 | 
			
		||||
 | 
			
		||||
	FfmpegParseMetaData(*format_context.streams[audio_stream],
 | 
			
		||||
				    rg, mr);
 | 
			
		||||
}
 | 
			
		||||
@@ -468,7 +472,7 @@ static bool
 | 
			
		||||
IsSeekable(const AVFormatContext &format_context) noexcept
 | 
			
		||||
{
 | 
			
		||||
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 6, 100)
 | 
			
		||||
	return (format_context.ctx_flags & AVFMTCTX_UNSEEKABLE) != 0;
 | 
			
		||||
	return (format_context.ctx_flags & AVFMTCTX_UNSEEKABLE) == 0;
 | 
			
		||||
#else
 | 
			
		||||
	(void)format_context;
 | 
			
		||||
	return false;
 | 
			
		||||
@@ -520,9 +524,15 @@ FfmpegDecode(DecoderClient &client, InputStream *input,
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
 | 
			
		||||
	const unsigned channels = codec_context->ch_layout.nb_channels;
 | 
			
		||||
#else
 | 
			
		||||
	const unsigned channels = codec_context->channels;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	const auto audio_format = CheckAudioFormat(codec_context->sample_rate,
 | 
			
		||||
						   sample_format,
 | 
			
		||||
						   codec_context->channels);
 | 
			
		||||
						   channels);
 | 
			
		||||
 | 
			
		||||
	const SignedSongTime total_time =
 | 
			
		||||
		av_stream.duration != (int64_t)AV_NOPTS_VALUE
 | 
			
		||||
@@ -530,9 +540,8 @@ FfmpegDecode(DecoderClient &client, InputStream *input,
 | 
			
		||||
		: FromFfmpegTimeChecked(format_context.duration, AV_TIME_BASE_Q);
 | 
			
		||||
 | 
			
		||||
	client.Ready(audio_format,
 | 
			
		||||
		     input
 | 
			
		||||
		     ? input->IsSeekable()
 | 
			
		||||
		     : IsSeekable(format_context),
 | 
			
		||||
		     (input ? input->IsSeekable() : false)
 | 
			
		||||
		     || IsSeekable(format_context),
 | 
			
		||||
		     total_time);
 | 
			
		||||
 | 
			
		||||
	FfmpegParseMetaData(client, format_context, audio_stream);
 | 
			
		||||
@@ -633,10 +642,17 @@ FfmpegScanStream(AVFormatContext &format_context, TagHandler &handler)
 | 
			
		||||
						  AV_TIME_BASE_Q));
 | 
			
		||||
 | 
			
		||||
	const auto &codec_params = *stream.codecpar;
 | 
			
		||||
 | 
			
		||||
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
 | 
			
		||||
	const unsigned channels = codec_params.ch_layout.nb_channels;
 | 
			
		||||
#else
 | 
			
		||||
	const unsigned channels = codec_params.channels;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	try {
 | 
			
		||||
		handler.OnAudioFormat(CheckAudioFormat(codec_params.sample_rate,
 | 
			
		||||
						       ffmpeg_sample_format(AVSampleFormat(codec_params.format)),
 | 
			
		||||
						       codec_params.channels));
 | 
			
		||||
						       channels));
 | 
			
		||||
	} catch (...) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -21,10 +21,16 @@
 | 
			
		||||
#define __STDC_CONSTANT_MACROS
 | 
			
		||||
 | 
			
		||||
#include "FfmpegIo.hxx"
 | 
			
		||||
#include "libavutil/mem.h"
 | 
			
		||||
#include "../DecoderAPI.hxx"
 | 
			
		||||
#include "input/InputStream.hxx"
 | 
			
		||||
 | 
			
		||||
extern "C" {
 | 
			
		||||
#include <libavutil/mem.h>
 | 
			
		||||
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(58, 29, 100)
 | 
			
		||||
#include <libavutil/error.h>
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AvioStream::~AvioStream()
 | 
			
		||||
{
 | 
			
		||||
	if (io != nullptr) {
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,7 @@
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
FlacDecoder::Initialize(unsigned sample_rate, unsigned bits_per_sample,
 | 
			
		||||
			unsigned channels, FLAC__uint64 total_frames)
 | 
			
		||||
			unsigned channels, FLAC__uint64 total_frames) noexcept
 | 
			
		||||
{
 | 
			
		||||
	assert(!initialized);
 | 
			
		||||
	assert(!unsupported);
 | 
			
		||||
@@ -60,7 +60,7 @@ FlacDecoder::Initialize(unsigned sample_rate, unsigned bits_per_sample,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline void
 | 
			
		||||
FlacDecoder::OnStreamInfo(const FLAC__StreamMetadata_StreamInfo &stream_info)
 | 
			
		||||
FlacDecoder::OnStreamInfo(const FLAC__StreamMetadata_StreamInfo &stream_info) noexcept
 | 
			
		||||
{
 | 
			
		||||
	if (initialized)
 | 
			
		||||
		return;
 | 
			
		||||
@@ -72,7 +72,7 @@ FlacDecoder::OnStreamInfo(const FLAC__StreamMetadata_StreamInfo &stream_info)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline void
 | 
			
		||||
FlacDecoder::OnVorbisComment(const FLAC__StreamMetadata_VorbisComment &vc)
 | 
			
		||||
FlacDecoder::OnVorbisComment(const FLAC__StreamMetadata_VorbisComment &vc) noexcept
 | 
			
		||||
{
 | 
			
		||||
	ReplayGainInfo rgi;
 | 
			
		||||
	if (flac_parse_replay_gain(rgi, vc))
 | 
			
		||||
@@ -86,7 +86,7 @@ FlacDecoder::OnVorbisComment(const FLAC__StreamMetadata_VorbisComment &vc)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
FlacDecoder::OnMetadata(const FLAC__StreamMetadata &metadata)
 | 
			
		||||
FlacDecoder::OnMetadata(const FLAC__StreamMetadata &metadata) noexcept
 | 
			
		||||
{
 | 
			
		||||
	if (unsupported)
 | 
			
		||||
		return;
 | 
			
		||||
@@ -106,7 +106,7 @@ FlacDecoder::OnMetadata(const FLAC__StreamMetadata &metadata)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline bool
 | 
			
		||||
FlacDecoder::OnFirstFrame(const FLAC__FrameHeader &header)
 | 
			
		||||
FlacDecoder::OnFirstFrame(const FLAC__FrameHeader &header) noexcept
 | 
			
		||||
{
 | 
			
		||||
	if (unsupported)
 | 
			
		||||
		return false;
 | 
			
		||||
@@ -139,7 +139,7 @@ FlacDecoder::GetDeltaPosition(const FLAC__StreamDecoder &sd)
 | 
			
		||||
FLAC__StreamDecoderWriteStatus
 | 
			
		||||
FlacDecoder::OnWrite(const FLAC__Frame &frame,
 | 
			
		||||
		     const FLAC__int32 *const buf[],
 | 
			
		||||
		     FLAC__uint64 nbytes)
 | 
			
		||||
		     FLAC__uint64 nbytes) noexcept
 | 
			
		||||
{
 | 
			
		||||
	if (!initialized && !OnFirstFrame(frame.header))
 | 
			
		||||
		return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
 | 
			
		||||
 
 | 
			
		||||
@@ -65,20 +65,21 @@ struct FlacDecoder : public FlacInput {
 | 
			
		||||
	 */
 | 
			
		||||
	ConstBuffer<void> chunk = nullptr;
 | 
			
		||||
 | 
			
		||||
	FlacDecoder(DecoderClient &_client, InputStream &_input_stream)
 | 
			
		||||
	FlacDecoder(DecoderClient &_client,
 | 
			
		||||
		    InputStream &_input_stream) noexcept
 | 
			
		||||
		:FlacInput(_input_stream, &_client) {}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Wrapper for DecoderClient::Ready().
 | 
			
		||||
	 */
 | 
			
		||||
	bool Initialize(unsigned sample_rate, unsigned bits_per_sample,
 | 
			
		||||
			unsigned channels, FLAC__uint64 total_frames);
 | 
			
		||||
			unsigned channels, FLAC__uint64 total_frames) noexcept;
 | 
			
		||||
 | 
			
		||||
	void OnMetadata(const FLAC__StreamMetadata &metadata);
 | 
			
		||||
	void OnMetadata(const FLAC__StreamMetadata &metadata) noexcept;
 | 
			
		||||
 | 
			
		||||
	FLAC__StreamDecoderWriteStatus OnWrite(const FLAC__Frame &frame,
 | 
			
		||||
					       const FLAC__int32 *const buf[],
 | 
			
		||||
					       FLAC__uint64 nbytes);
 | 
			
		||||
					       FLAC__uint64 nbytes) noexcept;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Calculate the delta (in bytes) between the last frame and
 | 
			
		||||
@@ -87,8 +88,8 @@ struct FlacDecoder : public FlacInput {
 | 
			
		||||
	FLAC__uint64 GetDeltaPosition(const FLAC__StreamDecoder &sd);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	void OnStreamInfo(const FLAC__StreamMetadata_StreamInfo &stream_info);
 | 
			
		||||
	void OnVorbisComment(const FLAC__StreamMetadata_VorbisComment &vc);
 | 
			
		||||
	void OnStreamInfo(const FLAC__StreamMetadata_StreamInfo &stream_info) noexcept;
 | 
			
		||||
	void OnVorbisComment(const FLAC__StreamMetadata_VorbisComment &vc) noexcept;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * This function attempts to call DecoderClient::Ready() in case there
 | 
			
		||||
@@ -97,7 +98,7 @@ private:
 | 
			
		||||
	 * providing the STREAMINFO block from the beginning of the file
 | 
			
		||||
	 * (e.g. when seeking with SqueezeBox Server).
 | 
			
		||||
	 */
 | 
			
		||||
	bool OnFirstFrame(const FLAC__FrameHeader &header);
 | 
			
		||||
	bool OnFirstFrame(const FLAC__FrameHeader &header) noexcept;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif /* _FLAC_COMMON_H */
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,7 @@
 | 
			
		||||
#include "lib/xiph/FlacMetadataChain.hxx"
 | 
			
		||||
#include "OggCodec.hxx"
 | 
			
		||||
#include "input/InputStream.hxx"
 | 
			
		||||
#include "input/LocalOpen.hxx"
 | 
			
		||||
#include "fs/Path.hxx"
 | 
			
		||||
#include "fs/NarrowPath.hxx"
 | 
			
		||||
#include "Log.hxx"
 | 
			
		||||
@@ -32,7 +33,8 @@
 | 
			
		||||
#error libFLAC is too old
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
static void flacPrintErroredState(FLAC__StreamDecoderState state)
 | 
			
		||||
static void
 | 
			
		||||
flacPrintErroredState(FLAC__StreamDecoderState state) noexcept
 | 
			
		||||
{
 | 
			
		||||
	switch (state) {
 | 
			
		||||
	case FLAC__STREAM_DECODER_SEARCH_FOR_METADATA:
 | 
			
		||||
@@ -53,8 +55,9 @@ static void flacPrintErroredState(FLAC__StreamDecoderState state)
 | 
			
		||||
	LogError(flac_domain, FLAC__StreamDecoderStateString[state]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void flacMetadata([[maybe_unused]] const FLAC__StreamDecoder * dec,
 | 
			
		||||
			 const FLAC__StreamMetadata * block, void *vdata)
 | 
			
		||||
static void
 | 
			
		||||
flacMetadata([[maybe_unused]] const FLAC__StreamDecoder * dec,
 | 
			
		||||
	     const FLAC__StreamMetadata * block, void *vdata) noexcept
 | 
			
		||||
{
 | 
			
		||||
	auto &fd = *(FlacDecoder *)vdata;
 | 
			
		||||
	fd.OnMetadata(*block);
 | 
			
		||||
@@ -62,29 +65,45 @@ static void flacMetadata([[maybe_unused]] const FLAC__StreamDecoder * dec,
 | 
			
		||||
 | 
			
		||||
static FLAC__StreamDecoderWriteStatus
 | 
			
		||||
flac_write_cb(const FLAC__StreamDecoder *dec, const FLAC__Frame *frame,
 | 
			
		||||
	      const FLAC__int32 *const buf[], void *vdata)
 | 
			
		||||
	      const FLAC__int32 *const buf[], void *vdata) noexcept
 | 
			
		||||
{
 | 
			
		||||
	auto &fd = *(FlacDecoder *)vdata;
 | 
			
		||||
	return fd.OnWrite(*frame, buf, fd.GetDeltaPosition(*dec));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool
 | 
			
		||||
flac_scan_file(Path path_fs, TagHandler &handler)
 | 
			
		||||
{
 | 
			
		||||
flac_scan_file(Path path_fs, TagHandler &handler) noexcept {
 | 
			
		||||
	FlacMetadataChain chain;
 | 
			
		||||
	if (!chain.Read(NarrowPath(path_fs))) {
 | 
			
		||||
	const bool succeed = [&chain, &path_fs]() noexcept {
 | 
			
		||||
		// read by NarrowPath
 | 
			
		||||
		if (chain.Read(NarrowPath(path_fs))) {
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
		if (std::is_same_v<Path::value_type, char> ||
 | 
			
		||||
		    chain.GetStatus() != FLAC__METADATA_CHAIN_STATUS_ERROR_OPENING_FILE) {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
		// read by InputStream
 | 
			
		||||
		Mutex mutex;
 | 
			
		||||
		auto is = OpenLocalInputStream(path_fs, mutex);
 | 
			
		||||
		if (is && chain.Read(*is)) {
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
		return false;
 | 
			
		||||
	}();
 | 
			
		||||
 | 
			
		||||
	if (!succeed) {
 | 
			
		||||
		FmtDebug(flac_domain,
 | 
			
		||||
			 "Failed to read FLAC tags: {}",
 | 
			
		||||
			 chain.GetStatusString());
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	chain.Scan(handler);
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool
 | 
			
		||||
flac_scan_stream(InputStream &is, TagHandler &handler)
 | 
			
		||||
flac_scan_stream(InputStream &is, TagHandler &handler) noexcept
 | 
			
		||||
{
 | 
			
		||||
	FlacMetadataChain chain;
 | 
			
		||||
	if (!chain.Read(is)) {
 | 
			
		||||
@@ -102,7 +121,7 @@ flac_scan_stream(InputStream &is, TagHandler &handler)
 | 
			
		||||
 * Some glue code around FLAC__stream_decoder_new().
 | 
			
		||||
 */
 | 
			
		||||
static FlacStreamDecoder
 | 
			
		||||
flac_decoder_new()
 | 
			
		||||
flac_decoder_new() noexcept
 | 
			
		||||
{
 | 
			
		||||
	FlacStreamDecoder sd;
 | 
			
		||||
	if(!FLAC__stream_decoder_set_metadata_respond(sd.get(), FLAC__METADATA_TYPE_VORBIS_COMMENT))
 | 
			
		||||
@@ -113,7 +132,7 @@ flac_decoder_new()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool
 | 
			
		||||
flac_decoder_initialize(FlacDecoder *data, FLAC__StreamDecoder *sd)
 | 
			
		||||
flac_decoder_initialize(FlacDecoder *data, FLAC__StreamDecoder *sd) noexcept
 | 
			
		||||
{
 | 
			
		||||
	if (!FLAC__stream_decoder_process_until_end_of_metadata(sd)) {
 | 
			
		||||
		if (FLAC__stream_decoder_get_state(sd) != FLAC__STREAM_DECODER_END_OF_STREAM)
 | 
			
		||||
@@ -231,7 +250,7 @@ flac_decoder_loop(FlacDecoder *data, FLAC__StreamDecoder *flac_dec)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static FLAC__StreamDecoderInitStatus
 | 
			
		||||
stream_init_oggflac(FLAC__StreamDecoder *flac_dec, FlacDecoder *data)
 | 
			
		||||
stream_init_oggflac(FLAC__StreamDecoder *flac_dec, FlacDecoder *data) noexcept
 | 
			
		||||
{
 | 
			
		||||
	return FLAC__stream_decoder_init_ogg_stream(flac_dec,
 | 
			
		||||
						    FlacInput::Read,
 | 
			
		||||
@@ -246,7 +265,7 @@ stream_init_oggflac(FLAC__StreamDecoder *flac_dec, FlacDecoder *data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static FLAC__StreamDecoderInitStatus
 | 
			
		||||
stream_init_flac(FLAC__StreamDecoder *flac_dec, FlacDecoder *data)
 | 
			
		||||
stream_init_flac(FLAC__StreamDecoder *flac_dec, FlacDecoder *data) noexcept
 | 
			
		||||
{
 | 
			
		||||
	return FLAC__stream_decoder_init_stream(flac_dec,
 | 
			
		||||
						FlacInput::Read,
 | 
			
		||||
@@ -261,7 +280,8 @@ stream_init_flac(FLAC__StreamDecoder *flac_dec, FlacDecoder *data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static FLAC__StreamDecoderInitStatus
 | 
			
		||||
stream_init(FLAC__StreamDecoder *flac_dec, FlacDecoder *data, bool is_ogg)
 | 
			
		||||
stream_init(FLAC__StreamDecoder *flac_dec, FlacDecoder *data,
 | 
			
		||||
	    bool is_ogg) noexcept
 | 
			
		||||
{
 | 
			
		||||
	return is_ogg
 | 
			
		||||
		? stream_init_oggflac(flac_dec, data)
 | 
			
		||||
@@ -307,7 +327,7 @@ flac_decode(DecoderClient &client, InputStream &input_stream)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool
 | 
			
		||||
oggflac_init([[maybe_unused]] const ConfigBlock &block)
 | 
			
		||||
oggflac_init([[maybe_unused]] const ConfigBlock &block) noexcept
 | 
			
		||||
{
 | 
			
		||||
	return !!FLAC_API_SUPPORTS_OGG_FLAC;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -22,12 +22,11 @@
 | 
			
		||||
#include "../DecoderAPI.hxx"
 | 
			
		||||
#include "input/InputStream.hxx"
 | 
			
		||||
#include "Log.hxx"
 | 
			
		||||
#include "util/Compiler.h"
 | 
			
		||||
 | 
			
		||||
#include <exception>
 | 
			
		||||
 | 
			
		||||
FLAC__StreamDecoderReadStatus
 | 
			
		||||
FlacInput::Read(FLAC__byte buffer[], size_t *bytes)
 | 
			
		||||
inline FLAC__StreamDecoderReadStatus
 | 
			
		||||
FlacInput::Read(FLAC__byte buffer[], size_t *bytes) noexcept
 | 
			
		||||
{
 | 
			
		||||
	size_t r = decoder_read(client, input_stream, (void *)buffer, *bytes);
 | 
			
		||||
	*bytes = r;
 | 
			
		||||
@@ -44,8 +43,8 @@ FlacInput::Read(FLAC__byte buffer[], size_t *bytes)
 | 
			
		||||
	return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FLAC__StreamDecoderSeekStatus
 | 
			
		||||
FlacInput::Seek(FLAC__uint64 absolute_byte_offset)
 | 
			
		||||
inline FLAC__StreamDecoderSeekStatus
 | 
			
		||||
FlacInput::Seek(FLAC__uint64 absolute_byte_offset) noexcept
 | 
			
		||||
{
 | 
			
		||||
	if (!input_stream.IsSeekable())
 | 
			
		||||
		return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED;
 | 
			
		||||
@@ -59,8 +58,8 @@ FlacInput::Seek(FLAC__uint64 absolute_byte_offset)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FLAC__StreamDecoderTellStatus
 | 
			
		||||
FlacInput::Tell(FLAC__uint64 *absolute_byte_offset)
 | 
			
		||||
inline FLAC__StreamDecoderTellStatus
 | 
			
		||||
FlacInput::Tell(FLAC__uint64 *absolute_byte_offset) noexcept
 | 
			
		||||
{
 | 
			
		||||
	if (!input_stream.IsSeekable())
 | 
			
		||||
		return FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED;
 | 
			
		||||
@@ -69,8 +68,8 @@ FlacInput::Tell(FLAC__uint64 *absolute_byte_offset)
 | 
			
		||||
	return FLAC__STREAM_DECODER_TELL_STATUS_OK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FLAC__StreamDecoderLengthStatus
 | 
			
		||||
FlacInput::Length(FLAC__uint64 *stream_length)
 | 
			
		||||
inline FLAC__StreamDecoderLengthStatus
 | 
			
		||||
FlacInput::Length(FLAC__uint64 *stream_length) noexcept
 | 
			
		||||
{
 | 
			
		||||
	if (!input_stream.KnownSize())
 | 
			
		||||
		return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED;
 | 
			
		||||
@@ -79,8 +78,8 @@ FlacInput::Length(FLAC__uint64 *stream_length)
 | 
			
		||||
	return FLAC__STREAM_DECODER_LENGTH_STATUS_OK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FLAC__bool
 | 
			
		||||
FlacInput::Eof()
 | 
			
		||||
inline FLAC__bool
 | 
			
		||||
FlacInput::Eof() noexcept
 | 
			
		||||
{
 | 
			
		||||
	return (client != nullptr &&
 | 
			
		||||
		client->GetCommand() != DecoderCommand::NONE &&
 | 
			
		||||
@@ -88,8 +87,8 @@ FlacInput::Eof()
 | 
			
		||||
		input_stream.LockIsEOF();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
FlacInput::Error(FLAC__StreamDecoderErrorStatus status)
 | 
			
		||||
inline void
 | 
			
		||||
FlacInput::Error(FLAC__StreamDecoderErrorStatus status) noexcept
 | 
			
		||||
{
 | 
			
		||||
	if (client == nullptr ||
 | 
			
		||||
	    client->GetCommand() != DecoderCommand::STOP)
 | 
			
		||||
@@ -100,7 +99,7 @@ FlacInput::Error(FLAC__StreamDecoderErrorStatus status)
 | 
			
		||||
FLAC__StreamDecoderReadStatus
 | 
			
		||||
FlacInput::Read([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder,
 | 
			
		||||
		FLAC__byte buffer[], size_t *bytes,
 | 
			
		||||
		void *client_data)
 | 
			
		||||
		void *client_data) noexcept
 | 
			
		||||
{
 | 
			
		||||
	auto *i = (FlacInput *)client_data;
 | 
			
		||||
 | 
			
		||||
@@ -109,7 +108,7 @@ FlacInput::Read([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder,
 | 
			
		||||
 | 
			
		||||
FLAC__StreamDecoderSeekStatus
 | 
			
		||||
FlacInput::Seek([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder,
 | 
			
		||||
		FLAC__uint64 absolute_byte_offset, void *client_data)
 | 
			
		||||
		FLAC__uint64 absolute_byte_offset, void *client_data) noexcept
 | 
			
		||||
{
 | 
			
		||||
	auto *i = (FlacInput *)client_data;
 | 
			
		||||
 | 
			
		||||
@@ -118,7 +117,7 @@ FlacInput::Seek([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder,
 | 
			
		||||
 | 
			
		||||
FLAC__StreamDecoderTellStatus
 | 
			
		||||
FlacInput::Tell([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder,
 | 
			
		||||
		FLAC__uint64 *absolute_byte_offset, void *client_data)
 | 
			
		||||
		FLAC__uint64 *absolute_byte_offset, void *client_data) noexcept
 | 
			
		||||
{
 | 
			
		||||
	auto *i = (FlacInput *)client_data;
 | 
			
		||||
 | 
			
		||||
@@ -127,7 +126,7 @@ FlacInput::Tell([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder,
 | 
			
		||||
 | 
			
		||||
FLAC__StreamDecoderLengthStatus
 | 
			
		||||
FlacInput::Length([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder,
 | 
			
		||||
		  FLAC__uint64 *stream_length, void *client_data)
 | 
			
		||||
		  FLAC__uint64 *stream_length, void *client_data) noexcept
 | 
			
		||||
{
 | 
			
		||||
	auto *i = (FlacInput *)client_data;
 | 
			
		||||
 | 
			
		||||
@@ -136,7 +135,7 @@ FlacInput::Length([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder,
 | 
			
		||||
 | 
			
		||||
FLAC__bool
 | 
			
		||||
FlacInput::Eof([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder,
 | 
			
		||||
	       void *client_data)
 | 
			
		||||
	       void *client_data) noexcept
 | 
			
		||||
{
 | 
			
		||||
	auto *i = (FlacInput *)client_data;
 | 
			
		||||
 | 
			
		||||
@@ -145,7 +144,8 @@ FlacInput::Eof([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder,
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
FlacInput::Error([[maybe_unused]] const FLAC__StreamDecoder *decoder,
 | 
			
		||||
		 FLAC__StreamDecoderErrorStatus status, void *client_data)
 | 
			
		||||
		 FLAC__StreamDecoderErrorStatus status,
 | 
			
		||||
		 void *client_data) noexcept
 | 
			
		||||
{
 | 
			
		||||
	auto *i = (FlacInput *)client_data;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -48,36 +48,38 @@ public:
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
	FLAC__StreamDecoderReadStatus Read(FLAC__byte buffer[], size_t *bytes);
 | 
			
		||||
	FLAC__StreamDecoderSeekStatus Seek(FLAC__uint64 absolute_byte_offset);
 | 
			
		||||
	FLAC__StreamDecoderTellStatus Tell(FLAC__uint64 *absolute_byte_offset);
 | 
			
		||||
	FLAC__StreamDecoderLengthStatus Length(FLAC__uint64 *stream_length);
 | 
			
		||||
	FLAC__bool Eof();
 | 
			
		||||
	void Error(FLAC__StreamDecoderErrorStatus status);
 | 
			
		||||
	FLAC__StreamDecoderReadStatus Read(FLAC__byte buffer[], size_t *bytes) noexcept;
 | 
			
		||||
	FLAC__StreamDecoderSeekStatus Seek(FLAC__uint64 absolute_byte_offset) noexcept;
 | 
			
		||||
	FLAC__StreamDecoderTellStatus Tell(FLAC__uint64 *absolute_byte_offset) noexcept;
 | 
			
		||||
	FLAC__StreamDecoderLengthStatus Length(FLAC__uint64 *stream_length) noexcept;
 | 
			
		||||
	FLAC__bool Eof() noexcept;
 | 
			
		||||
	void Error(FLAC__StreamDecoderErrorStatus status) noexcept;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	static FLAC__StreamDecoderReadStatus
 | 
			
		||||
	Read(const FLAC__StreamDecoder *flac_decoder,
 | 
			
		||||
	     FLAC__byte buffer[], size_t *bytes, void *client_data);
 | 
			
		||||
	     FLAC__byte buffer[], size_t *bytes, void *client_data) noexcept;
 | 
			
		||||
 | 
			
		||||
	static FLAC__StreamDecoderSeekStatus
 | 
			
		||||
	Seek(const FLAC__StreamDecoder *flac_decoder,
 | 
			
		||||
	     FLAC__uint64 absolute_byte_offset, void *client_data);
 | 
			
		||||
	     FLAC__uint64 absolute_byte_offset, void *client_data) noexcept;
 | 
			
		||||
 | 
			
		||||
	static FLAC__StreamDecoderTellStatus
 | 
			
		||||
	Tell(const FLAC__StreamDecoder *flac_decoder,
 | 
			
		||||
	     FLAC__uint64 *absolute_byte_offset, void *client_data);
 | 
			
		||||
	     FLAC__uint64 *absolute_byte_offset, void *client_data) noexcept;
 | 
			
		||||
 | 
			
		||||
	static FLAC__StreamDecoderLengthStatus
 | 
			
		||||
	Length(const FLAC__StreamDecoder *flac_decoder,
 | 
			
		||||
	       FLAC__uint64 *stream_length, void *client_data);
 | 
			
		||||
	       FLAC__uint64 *stream_length, void *client_data) noexcept;
 | 
			
		||||
 | 
			
		||||
	static FLAC__bool
 | 
			
		||||
	Eof(const FLAC__StreamDecoder *flac_decoder, void *client_data);
 | 
			
		||||
	Eof(const FLAC__StreamDecoder *flac_decoder,
 | 
			
		||||
	    void *client_data) noexcept;
 | 
			
		||||
 | 
			
		||||
	static void
 | 
			
		||||
	Error(const FLAC__StreamDecoder *decoder,
 | 
			
		||||
	      FLAC__StreamDecoderErrorStatus status, void *client_data);
 | 
			
		||||
	      FLAC__StreamDecoderErrorStatus status,
 | 
			
		||||
	      void *client_data) noexcept;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -39,7 +39,8 @@ FlacPcmImport::Open(unsigned sample_rate, unsigned bits_per_sample,
 | 
			
		||||
 | 
			
		||||
template<typename T>
 | 
			
		||||
static void
 | 
			
		||||
FlacImportStereo(T *dest, const FLAC__int32 *const src[], size_t n_frames)
 | 
			
		||||
FlacImportStereo(T *dest, const FLAC__int32 *const src[],
 | 
			
		||||
		 size_t n_frames) noexcept
 | 
			
		||||
{
 | 
			
		||||
	for (size_t i = 0; i != n_frames; ++i) {
 | 
			
		||||
		*dest++ = (T)src[0][i];
 | 
			
		||||
@@ -50,7 +51,7 @@ FlacImportStereo(T *dest, const FLAC__int32 *const src[], size_t n_frames)
 | 
			
		||||
template<typename T>
 | 
			
		||||
static void
 | 
			
		||||
FlacImportAny(T *dest, const FLAC__int32 *const src[], size_t n_frames,
 | 
			
		||||
	      unsigned n_channels)
 | 
			
		||||
	      unsigned n_channels) noexcept
 | 
			
		||||
{
 | 
			
		||||
	for (size_t i = 0; i != n_frames; ++i)
 | 
			
		||||
		for (unsigned c = 0; c != n_channels; ++c)
 | 
			
		||||
@@ -60,7 +61,7 @@ FlacImportAny(T *dest, const FLAC__int32 *const src[], size_t n_frames,
 | 
			
		||||
template<typename T>
 | 
			
		||||
static void
 | 
			
		||||
FlacImport(T *dest, const FLAC__int32 *const src[], size_t n_frames,
 | 
			
		||||
	   unsigned n_channels)
 | 
			
		||||
	   unsigned n_channels) noexcept
 | 
			
		||||
{
 | 
			
		||||
	if (n_channels == 2)
 | 
			
		||||
		FlacImportStereo(dest, src, n_frames);
 | 
			
		||||
@@ -71,7 +72,7 @@ FlacImport(T *dest, const FLAC__int32 *const src[], size_t n_frames,
 | 
			
		||||
template<typename T>
 | 
			
		||||
static ConstBuffer<void>
 | 
			
		||||
FlacImport(PcmBuffer &buffer, const FLAC__int32 *const src[], size_t n_frames,
 | 
			
		||||
	   unsigned n_channels)
 | 
			
		||||
	   unsigned n_channels) noexcept
 | 
			
		||||
{
 | 
			
		||||
	size_t n_samples = n_frames * n_channels;
 | 
			
		||||
	size_t dest_size = n_samples * sizeof(T);
 | 
			
		||||
 
 | 
			
		||||
@@ -43,7 +43,7 @@ public:
 | 
			
		||||
	void Open(unsigned sample_rate, unsigned bits_per_sample,
 | 
			
		||||
		  unsigned channels);
 | 
			
		||||
 | 
			
		||||
	const AudioFormat &GetAudioFormat() const {
 | 
			
		||||
	const AudioFormat &GetAudioFormat() const noexcept {
 | 
			
		||||
		return audio_format;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -56,20 +56,17 @@ struct GmeContainerPath {
 | 
			
		||||
	unsigned track;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#if GME_VERSION >= 0x000600
 | 
			
		||||
static int gme_accuracy;
 | 
			
		||||
#endif
 | 
			
		||||
static unsigned gme_default_fade;
 | 
			
		||||
 | 
			
		||||
static bool
 | 
			
		||||
gme_plugin_init([[maybe_unused]] const ConfigBlock &block)
 | 
			
		||||
{
 | 
			
		||||
#if GME_VERSION >= 0x000600
 | 
			
		||||
	auto accuracy = block.GetBlockParam("accuracy");
 | 
			
		||||
	gme_accuracy = accuracy != nullptr
 | 
			
		||||
		? (int)accuracy->GetBoolValue()
 | 
			
		||||
		: -1;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	auto fade = block.GetBlockParam("default_fade");
 | 
			
		||||
	gme_default_fade = fade != nullptr
 | 
			
		||||
		? fade->GetUnsignedValue() * 1000
 | 
			
		||||
@@ -163,10 +160,8 @@ gme_file_decode(DecoderClient &client, Path path_fs)
 | 
			
		||||
	FmtDebug(gme_domain, "emulator type '{}'",
 | 
			
		||||
		 gme_type_system(gme_type(emu)));
 | 
			
		||||
 | 
			
		||||
#if GME_VERSION >= 0x000600
 | 
			
		||||
	if (gme_accuracy >= 0)
 | 
			
		||||
		gme_enable_accuracy(emu, gme_accuracy);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	gme_info_t *ti;
 | 
			
		||||
	const char *gme_err = gme_track_info(emu, &ti, container.track);
 | 
			
		||||
 
 | 
			
		||||
@@ -562,7 +562,21 @@ parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen) noexcept
 | 
			
		||||
 | 
			
		||||
	mad_bit_skip(ptr, 16);
 | 
			
		||||
 | 
			
		||||
	lame->peak = MAD_F(mad_bit_read(ptr, 32) << 5); /* peak */
 | 
			
		||||
	/* The lame peak value is a float multiplied by 2^23 and stored as an
 | 
			
		||||
	 * unsigned integer (it is always positive). MAD's fixed-point format uses
 | 
			
		||||
	 * 28 bits for the fractional part, so shift the 23 bit fraction up before
 | 
			
		||||
	 * converting to a float.
 | 
			
		||||
	 */
 | 
			
		||||
	unsigned long peak_int = mad_bit_read(ptr, 32);
 | 
			
		||||
 | 
			
		||||
#define LAME_PEAK_FRACBITS 23
 | 
			
		||||
#if MAD_F_FRACBITS > LAME_PEAK_FRACBITS
 | 
			
		||||
	peak_int <<= (MAD_F_FRACBITS - LAME_PEAK_FRACBITS);
 | 
			
		||||
#elif LAME_PEAK_FRACBITS > MAD_F_FRACBITS
 | 
			
		||||
	peak_int >>= (LAME_PEAK_FRACBITS - MAD_F_FRACBITS);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	lame->peak = mad_f_todouble(peak_int); /* peak */
 | 
			
		||||
	FmtDebug(mad_domain, "LAME peak found: {}", lame->peak);
 | 
			
		||||
 | 
			
		||||
	lame->track_gain = 0;
 | 
			
		||||
@@ -798,6 +812,8 @@ MadDecoder::UpdateTimerNextFrame() noexcept
 | 
			
		||||
DecoderCommand
 | 
			
		||||
MadDecoder::SubmitPCM(size_t i, size_t pcm_length) noexcept
 | 
			
		||||
{
 | 
			
		||||
	assert(i <= pcm_length);
 | 
			
		||||
 | 
			
		||||
	size_t num_samples = pcm_length - i;
 | 
			
		||||
 | 
			
		||||
	mad_fixed_to_24_buffer(output_buffer, synth.pcm,
 | 
			
		||||
@@ -843,7 +859,7 @@ MadDecoder::SynthAndSubmit() noexcept
 | 
			
		||||
	size_t pcm_length = synth.pcm.length;
 | 
			
		||||
	if (drop_end_samples &&
 | 
			
		||||
	    current_frame == max_frames - drop_end_frames - 1) {
 | 
			
		||||
		if (drop_end_samples >= pcm_length)
 | 
			
		||||
		if (i + drop_end_samples >= pcm_length)
 | 
			
		||||
			return DecoderCommand::STOP;
 | 
			
		||||
 | 
			
		||||
		pcm_length -= drop_end_samples;
 | 
			
		||||
 
 | 
			
		||||
@@ -81,7 +81,7 @@ if libfaad_dep.found()
 | 
			
		||||
  decoder_plugins_sources += 'FaadDecoderPlugin.cxx'
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
libgme_dep = c_compiler.find_library('gme', required: get_option('gme'))
 | 
			
		||||
libgme_dep = dependency('libgme', version: '>= 0.6', required: get_option('gme'))
 | 
			
		||||
decoder_features.set('ENABLE_GME', libgme_dep.found())
 | 
			
		||||
if libgme_dep.found()
 | 
			
		||||
  decoder_plugins_sources += 'GmeDecoderPlugin.cxx'
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,23 @@ encoder_features = configuration_data()
 | 
			
		||||
encoder_features.set('ENABLE_ENCODER', need_encoder)
 | 
			
		||||
 | 
			
		||||
if not need_encoder
 | 
			
		||||
  if need_wave_encoder
 | 
			
		||||
    # Special case for the Snapcast output plugin which only needs the
 | 
			
		||||
    # PCM wave encoder encoder plugin
 | 
			
		||||
    encoder_glue = static_library(
 | 
			
		||||
      'encoder_glue',
 | 
			
		||||
      'plugins/WaveEncoderPlugin.cxx',
 | 
			
		||||
      include_directories: inc,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    encoder_glue_dep = declare_dependency(
 | 
			
		||||
      link_with: encoder_glue,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    configure_file(output: 'Features.h', configuration: encoder_features)
 | 
			
		||||
    subdir_done()
 | 
			
		||||
  endif
 | 
			
		||||
 | 
			
		||||
  encoder_glue_dep = dependency('', required: false)
 | 
			
		||||
  configure_file(output: 'Features.h', configuration: encoder_features)
 | 
			
		||||
  subdir_done()
 | 
			
		||||
 
 | 
			
		||||
@@ -38,6 +38,7 @@ class FlacEncoder final : public Encoder {
 | 
			
		||||
 | 
			
		||||
	FLAC__StreamEncoder *const fse;
 | 
			
		||||
	const unsigned compression;
 | 
			
		||||
	const bool oggflac;
 | 
			
		||||
 | 
			
		||||
	PcmBuffer expand_buffer;
 | 
			
		||||
 | 
			
		||||
@@ -122,7 +123,7 @@ flac_encoder_init(const ConfigBlock &block)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
flac_encoder_setup(FLAC__StreamEncoder *fse, unsigned compression,
 | 
			
		||||
flac_encoder_setup(FLAC__StreamEncoder *fse, unsigned compression, bool oggflac,
 | 
			
		||||
		   const AudioFormat &audio_format)
 | 
			
		||||
{
 | 
			
		||||
	unsigned bits_per_sample;
 | 
			
		||||
@@ -157,7 +158,7 @@ flac_encoder_setup(FLAC__StreamEncoder *fse, unsigned compression,
 | 
			
		||||
		throw FormatRuntimeError("error setting flac sample rate to %d",
 | 
			
		||||
					 audio_format.sample_rate);
 | 
			
		||||
 | 
			
		||||
	if (!FLAC__stream_encoder_set_ogg_serial_number(fse,
 | 
			
		||||
	if (oggflac && !FLAC__stream_encoder_set_ogg_serial_number(fse,
 | 
			
		||||
						  GenerateSerial()))
 | 
			
		||||
		throw FormatRuntimeError("error setting ogg serial number");
 | 
			
		||||
}
 | 
			
		||||
@@ -166,11 +167,12 @@ FlacEncoder::FlacEncoder(AudioFormat _audio_format, FLAC__StreamEncoder *_fse, u
 | 
			
		||||
	:Encoder(_oggchaining),
 | 
			
		||||
	 audio_format(_audio_format), fse(_fse),
 | 
			
		||||
	 compression(_compression),
 | 
			
		||||
	 oggflac(_oggflac),
 | 
			
		||||
	 output_buffer(8192)
 | 
			
		||||
{
 | 
			
		||||
	/* this immediately outputs data through callback */
 | 
			
		||||
 | 
			
		||||
	auto init_status = _oggflac ?
 | 
			
		||||
	auto init_status = oggflac ?
 | 
			
		||||
		FLAC__stream_encoder_init_ogg_stream(fse,
 | 
			
		||||
						     nullptr, WriteCallback,
 | 
			
		||||
						     nullptr, nullptr, nullptr,
 | 
			
		||||
@@ -209,7 +211,7 @@ PreparedFlacEncoder::Open(AudioFormat &audio_format)
 | 
			
		||||
		throw std::runtime_error("FLAC__stream_encoder_new() failed");
 | 
			
		||||
 | 
			
		||||
	try {
 | 
			
		||||
		flac_encoder_setup(fse, compression, audio_format);
 | 
			
		||||
		flac_encoder_setup(fse, compression, oggflac, audio_format);
 | 
			
		||||
	} catch (...) {
 | 
			
		||||
		FLAC__stream_encoder_delete(fse);
 | 
			
		||||
		throw;
 | 
			
		||||
@@ -222,7 +224,7 @@ void
 | 
			
		||||
FlacEncoder::SendTag(const Tag &tag)
 | 
			
		||||
{
 | 
			
		||||
	/* re-initialize encoder since flac_encoder_finish resets everything */
 | 
			
		||||
	flac_encoder_setup(fse, compression, audio_format);
 | 
			
		||||
	flac_encoder_setup(fse, compression, oggflac, audio_format);
 | 
			
		||||
 | 
			
		||||
	FLAC__StreamMetadata *metadata = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT);
 | 
			
		||||
	FLAC__StreamMetadata_VorbisComment_Entry entry;
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,7 @@ if libshine_dep.found()
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
encoder_features.set('ENABLE_WAVE_ENCODER', get_option('wave_encoder'))
 | 
			
		||||
if get_option('wave_encoder')
 | 
			
		||||
if get_option('wave_encoder') or need_wave_encoder
 | 
			
		||||
  encoder_plugins_sources += 'WaveEncoderPlugin.cxx'
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -272,9 +272,8 @@ EventLoop::Run() noexcept
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	assert(IsInside());
 | 
			
		||||
	assert(!quit);
 | 
			
		||||
#ifdef HAVE_THREADED_EVENT_LOOP
 | 
			
		||||
	assert(alive);
 | 
			
		||||
	assert(alive || quit);
 | 
			
		||||
	assert(busy);
 | 
			
		||||
 | 
			
		||||
	wake_event.Schedule(SocketEvent::READ);
 | 
			
		||||
@@ -299,7 +298,7 @@ EventLoop::Run() noexcept
 | 
			
		||||
 | 
			
		||||
	steady_clock_cache.flush();
 | 
			
		||||
 | 
			
		||||
	do {
 | 
			
		||||
	while (!quit) {
 | 
			
		||||
		again = false;
 | 
			
		||||
 | 
			
		||||
		/* invoke timers */
 | 
			
		||||
@@ -361,7 +360,7 @@ EventLoop::Run() noexcept
 | 
			
		||||
 | 
			
		||||
			socket_event.Dispatch();
 | 
			
		||||
		}
 | 
			
		||||
	} while (!quit);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
#ifdef HAVE_THREADED_EVENT_LOOP
 | 
			
		||||
#ifndef NDEBUG
 | 
			
		||||
 
 | 
			
		||||
@@ -40,10 +40,15 @@ FfmpegFilter::FfmpegFilter(const AudioFormat &in_audio_format,
 | 
			
		||||
	 buffer_sink(_buffer_sink),
 | 
			
		||||
	 in_format(Ffmpeg::ToFfmpegSampleFormat(in_audio_format.format)),
 | 
			
		||||
	 in_sample_rate(in_audio_format.sample_rate),
 | 
			
		||||
#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 25, 100)
 | 
			
		||||
	 in_channels(in_audio_format.channels),
 | 
			
		||||
#endif
 | 
			
		||||
	 in_audio_frame_size(in_audio_format.GetFrameSize()),
 | 
			
		||||
	 out_audio_frame_size(_out_audio_format.GetFrameSize())
 | 
			
		||||
{
 | 
			
		||||
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
 | 
			
		||||
	av_channel_layout_default(&in_ch_layout, in_audio_format.channels);
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ConstBuffer<void>
 | 
			
		||||
@@ -54,7 +59,11 @@ FfmpegFilter::FilterPCM(ConstBuffer<void> src)
 | 
			
		||||
	frame.Unref();
 | 
			
		||||
	frame->format = in_format;
 | 
			
		||||
	frame->sample_rate = in_sample_rate;
 | 
			
		||||
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
 | 
			
		||||
	frame->ch_layout = in_ch_layout;
 | 
			
		||||
#else
 | 
			
		||||
	frame->channels = in_channels;
 | 
			
		||||
#endif
 | 
			
		||||
	frame->nb_samples = src.size / in_audio_frame_size;
 | 
			
		||||
 | 
			
		||||
	frame.GetBuffer();
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,13 @@ class FfmpegFilter final : public Filter {
 | 
			
		||||
 | 
			
		||||
	FfmpegBuffer interleave_buffer;
 | 
			
		||||
 | 
			
		||||
	const int in_format, in_sample_rate, in_channels;
 | 
			
		||||
	const int in_format, in_sample_rate;
 | 
			
		||||
 | 
			
		||||
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
 | 
			
		||||
	AVChannelLayout in_ch_layout;
 | 
			
		||||
#else
 | 
			
		||||
	const int in_channels;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	const size_t in_audio_frame_size;
 | 
			
		||||
	const size_t out_audio_frame_size;
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,8 @@
 | 
			
		||||
#include "ReplayGainInfo.hxx"
 | 
			
		||||
#include "ReplayGainConfig.hxx"
 | 
			
		||||
#include "mixer/MixerControl.hxx"
 | 
			
		||||
#include "mixer/MixerInternal.hxx"
 | 
			
		||||
#include "mixer/Listener.hxx"
 | 
			
		||||
#include "pcm/AudioFormat.hxx"
 | 
			
		||||
#include "pcm/Volume.hxx"
 | 
			
		||||
#include "util/ConstBuffer.hxx"
 | 
			
		||||
@@ -171,9 +173,11 @@ ReplayGainFilter::Update()
 | 
			
		||||
		try {
 | 
			
		||||
			mixer_set_volume(mixer, _volume);
 | 
			
		||||
 | 
			
		||||
			/* TODO: emit this idle event only for the
 | 
			
		||||
			   current partition */
 | 
			
		||||
			idle_add(IDLE_MIXER);
 | 
			
		||||
			/* invoke the mixer's listener manually, just
 | 
			
		||||
			   in case the mixer implementation didn't do
 | 
			
		||||
			   that already (this depends on the
 | 
			
		||||
			   implementation) */
 | 
			
		||||
			mixer->listener.OnMixerVolumeChanged(*mixer, _volume);
 | 
			
		||||
		} catch (...) {
 | 
			
		||||
			LogError(std::current_exception(),
 | 
			
		||||
				 "Failed to update hardware mixer");
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,6 @@
 | 
			
		||||
#include "Charset.hxx"
 | 
			
		||||
#include "Features.hxx"
 | 
			
		||||
#include "Domain.hxx"
 | 
			
		||||
#include "Log.hxx"
 | 
			
		||||
#include "lib/icu/Converter.hxx"
 | 
			
		||||
#include "util/AllocatedString.hxx"
 | 
			
		||||
#include "config.h"
 | 
			
		||||
@@ -45,11 +44,9 @@ SetFSCharset(const char *charset)
 | 
			
		||||
	assert(charset != nullptr);
 | 
			
		||||
	assert(fs_converter == nullptr);
 | 
			
		||||
 | 
			
		||||
	fs_charset = charset;
 | 
			
		||||
	fs_converter = IcuConverter::Create(charset);
 | 
			
		||||
	assert(fs_converter != nullptr);
 | 
			
		||||
 | 
			
		||||
	FmtDebug(path_domain,
 | 
			
		||||
		 "SetFSCharset: fs charset is {}", fs_charset);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -67,6 +67,16 @@ StatFile(Path file, struct stat &buf, bool follow_symlinks = true)
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
static inline bool
 | 
			
		||||
CreateDirectoryNoThrow(Path path) noexcept
 | 
			
		||||
{
 | 
			
		||||
#ifdef _WIN32
 | 
			
		||||
	return CreateDirectory(path.c_str(), nullptr);
 | 
			
		||||
#else
 | 
			
		||||
	return mkdir(path.c_str(), 0777);
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Truncate a file that exists already.  Throws std::system_error on
 | 
			
		||||
 * error.
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,6 @@
 | 
			
		||||
#include <shlobj.h>
 | 
			
		||||
#else
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
#include <pwd.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
@@ -53,6 +52,12 @@
 | 
			
		||||
#include "Main.hxx"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_XDG
 | 
			
		||||
#include "Version.h" // for PACKAGE_NAME
 | 
			
		||||
#define APP_FILENAME PATH_LITERAL(PACKAGE_NAME)
 | 
			
		||||
static constexpr Path app_filename = Path::FromFS(APP_FILENAME);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#if !defined(_WIN32) && !defined(ANDROID)
 | 
			
		||||
class PasswdEntry
 | 
			
		||||
{
 | 
			
		||||
@@ -74,15 +79,6 @@ public:
 | 
			
		||||
		return result != nullptr;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool ReadByUid(uid_t uid) {
 | 
			
		||||
#ifdef HAVE_GETPWUID_R
 | 
			
		||||
		getpwuid_r(uid, &pw, buf.data(), buf.size(), &result);
 | 
			
		||||
#else
 | 
			
		||||
		result = getpwuid(uid);
 | 
			
		||||
#endif
 | 
			
		||||
		return result != nullptr;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const passwd *operator->() {
 | 
			
		||||
		assert(result != nullptr);
 | 
			
		||||
		return result;
 | 
			
		||||
@@ -254,7 +250,8 @@ GetUserMusicDir() noexcept
 | 
			
		||||
#elif defined(USE_XDG)
 | 
			
		||||
	return GetUserDir("XDG_MUSIC_DIR");
 | 
			
		||||
#elif defined(ANDROID)
 | 
			
		||||
	return Environment::getExternalStoragePublicDirectory("Music");
 | 
			
		||||
	return Environment::getExternalStoragePublicDirectory(Java::GetEnv(),
 | 
			
		||||
							      "Music");
 | 
			
		||||
#else
 | 
			
		||||
	return nullptr;
 | 
			
		||||
#endif
 | 
			
		||||
@@ -283,6 +280,24 @@ GetUserCacheDir() noexcept
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AllocatedPath
 | 
			
		||||
GetAppCacheDir() noexcept
 | 
			
		||||
{
 | 
			
		||||
#ifdef USE_XDG
 | 
			
		||||
	if (const auto user_dir = GetUserCacheDir(); !user_dir.IsNull()) {
 | 
			
		||||
		auto dir = user_dir / app_filename;
 | 
			
		||||
		CreateDirectoryNoThrow(dir);
 | 
			
		||||
		return dir;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nullptr;
 | 
			
		||||
#elif defined(ANDROID)
 | 
			
		||||
	return context->GetCacheDir(Java::GetEnv());
 | 
			
		||||
#else
 | 
			
		||||
	return nullptr;
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AllocatedPath
 | 
			
		||||
GetUserRuntimeDir() noexcept
 | 
			
		||||
{
 | 
			
		||||
@@ -296,7 +311,7 @@ GetUserRuntimeDir() noexcept
 | 
			
		||||
AllocatedPath
 | 
			
		||||
GetAppRuntimeDir() noexcept
 | 
			
		||||
{
 | 
			
		||||
#ifdef __linux__
 | 
			
		||||
#if defined(__linux__) && !defined(ANDROID)
 | 
			
		||||
	/* systemd specific; see systemd.exec(5) */
 | 
			
		||||
	if (const char *runtime_directory = getenv("RUNTIME_DIRECTORY"))
 | 
			
		||||
		if (auto dir = StringView{runtime_directory}.Split(':').first;
 | 
			
		||||
@@ -306,8 +321,8 @@ GetAppRuntimeDir() noexcept
 | 
			
		||||
 | 
			
		||||
#ifdef USE_XDG
 | 
			
		||||
	if (const auto user_dir = GetUserRuntimeDir(); !user_dir.IsNull()) {
 | 
			
		||||
		auto dir = user_dir / Path::FromFS("mpd");
 | 
			
		||||
		mkdir(dir.c_str(), 0700);
 | 
			
		||||
		auto dir = user_dir / app_filename;
 | 
			
		||||
		CreateDirectoryNoThrow(dir);
 | 
			
		||||
		return dir;
 | 
			
		||||
	}
 | 
			
		||||
#endif
 | 
			
		||||
@@ -350,10 +365,8 @@ GetHomeDir() noexcept
 | 
			
		||||
	if (const auto home = getenv("HOME");
 | 
			
		||||
	    IsValidPathString(home) && IsValidDir(home))
 | 
			
		||||
		return AllocatedPath::FromFS(home);
 | 
			
		||||
 | 
			
		||||
	if (PasswdEntry pw; pw.ReadByUid(getuid()))
 | 
			
		||||
		return SafePathFromFS(pw->pw_dir);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -43,6 +43,13 @@ GetUserMusicDir() noexcept;
 | 
			
		||||
AllocatedPath
 | 
			
		||||
GetUserCacheDir() noexcept;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Obtains cache directory for this application.
 | 
			
		||||
 */
 | 
			
		||||
[[gnu::const]]
 | 
			
		||||
AllocatedPath
 | 
			
		||||
GetAppCacheDir() noexcept;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Obtains the runtime directory for the current user.
 | 
			
		||||
 */
 | 
			
		||||
 
 | 
			
		||||
@@ -101,9 +101,17 @@ AsyncInputStream::Seek(std::unique_lock<Mutex> &lock,
 | 
			
		||||
	assert(IsReady());
 | 
			
		||||
	assert(seek_state == SeekState::NONE);
 | 
			
		||||
 | 
			
		||||
	if (new_offset == offset)
 | 
			
		||||
		/* no-op */
 | 
			
		||||
	if (new_offset == offset) {
 | 
			
		||||
		/* no-op, but if the stream is not open anymore (maybe
 | 
			
		||||
		   because it has failed), nothing can be read, so we
 | 
			
		||||
		   should check for errors here instead of pretending
 | 
			
		||||
		   everything's fine */
 | 
			
		||||
 | 
			
		||||
		if (!open)
 | 
			
		||||
			Check();
 | 
			
		||||
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!IsSeekable())
 | 
			
		||||
		throw std::runtime_error("Not seekable");
 | 
			
		||||
 
 | 
			
		||||
@@ -234,7 +234,7 @@ AlsaInputStream::PrepareSockets() noexcept
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
AlsaInputStream::DispatchSockets() noexcept
 | 
			
		||||
{
 | 
			
		||||
try {
 | 
			
		||||
	non_block.DispatchSockets(*this, capture_handle);
 | 
			
		||||
 | 
			
		||||
	const std::scoped_lock<Mutex> protect(mutex);
 | 
			
		||||
@@ -253,16 +253,17 @@ AlsaInputStream::DispatchSockets() noexcept
 | 
			
		||||
		if (n_frames == -EAGAIN)
 | 
			
		||||
			return;
 | 
			
		||||
 | 
			
		||||
		if (Recover(n_frames) < 0) {
 | 
			
		||||
			postponed_exception = std::make_exception_ptr(std::runtime_error("PCM error - stream aborted"));
 | 
			
		||||
			InvokeOnAvailable();
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		if (Recover(n_frames) < 0)
 | 
			
		||||
			throw std::runtime_error("PCM error - stream aborted");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	size_t nbytes = n_frames * frame_size;
 | 
			
		||||
	CommitWriteBuffer(nbytes);
 | 
			
		||||
}
 | 
			
		||||
catch (...) {
 | 
			
		||||
	postponed_exception = std::current_exception();
 | 
			
		||||
	InvokeOnAvailable();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline int
 | 
			
		||||
AlsaInputStream::Recover(int err)
 | 
			
		||||
@@ -369,9 +370,14 @@ AlsaInputStream::ConfigureCapture(AudioFormat audio_format)
 | 
			
		||||
		 period_size_min, period_size_max,
 | 
			
		||||
		 period_time_min, period_time_max);
 | 
			
		||||
 | 
			
		||||
	/* choose the maximum possible buffer_size ... */
 | 
			
		||||
	snd_pcm_hw_params_set_buffer_size(capture_handle, hw_params,
 | 
			
		||||
					  buffer_size_max);
 | 
			
		||||
	/* choose the maximum buffer_time up to limit of 2 seconds ... */
 | 
			
		||||
	unsigned buffer_time = buffer_time_max;
 | 
			
		||||
	if (buffer_time > 2000000U)
 | 
			
		||||
		buffer_time = 2000000U;
 | 
			
		||||
	int direction = -1;
 | 
			
		||||
	if ((err = snd_pcm_hw_params_set_buffer_time_near(capture_handle,
 | 
			
		||||
				hw_params, &buffer_time, &direction)) < 0)
 | 
			
		||||
		throw Alsa::MakeError(err, "Cannot set buffer time");
 | 
			
		||||
 | 
			
		||||
	/* ... and calculate the period_size to have four periods in
 | 
			
		||||
	   one buffer; this way, we get woken up often enough to avoid
 | 
			
		||||
@@ -379,7 +385,7 @@ AlsaInputStream::ConfigureCapture(AudioFormat audio_format)
 | 
			
		||||
	snd_pcm_uframes_t buffer_size;
 | 
			
		||||
	if (snd_pcm_hw_params_get_buffer_size(hw_params, &buffer_size) == 0) {
 | 
			
		||||
		snd_pcm_uframes_t period_size = buffer_size / 4;
 | 
			
		||||
		int direction = -1;
 | 
			
		||||
		direction = -1;
 | 
			
		||||
		if ((err = snd_pcm_hw_params_set_period_size_near(capture_handle,
 | 
			
		||||
		                             hw_params, &period_size, &direction)) < 0)
 | 
			
		||||
			throw Alsa::MakeError(err, "Cannot set period size");
 | 
			
		||||
 
 | 
			
		||||
@@ -30,10 +30,12 @@
 | 
			
		||||
#include "util/RuntimeError.hxx"
 | 
			
		||||
#include "util/Domain.hxx"
 | 
			
		||||
#include "util/ByteOrder.hxx"
 | 
			
		||||
#include "util/ScopeExit.hxx"
 | 
			
		||||
#include "fs/AllocatedPath.hxx"
 | 
			
		||||
#include "Log.hxx"
 | 
			
		||||
#include "config/Block.hxx"
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <cassert>
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
 | 
			
		||||
@@ -43,31 +45,35 @@
 | 
			
		||||
 | 
			
		||||
#include <cdio/cd_types.h>
 | 
			
		||||
 | 
			
		||||
static constexpr Domain cdio_domain("cdio");
 | 
			
		||||
 | 
			
		||||
static bool default_reverse_endian;
 | 
			
		||||
static unsigned speed = 0;
 | 
			
		||||
 | 
			
		||||
/* Default to full paranoia, but allow skipping sectors. */
 | 
			
		||||
static int mode_flags = PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP;
 | 
			
		||||
 | 
			
		||||
class CdioParanoiaInputStream final : public InputStream {
 | 
			
		||||
	cdrom_drive_t *const drv;
 | 
			
		||||
	CdIo_t *const cdio;
 | 
			
		||||
	CdromParanoia para;
 | 
			
		||||
 | 
			
		||||
	const lsn_t lsn_from, lsn_to;
 | 
			
		||||
	int lsn_relofs;
 | 
			
		||||
	const lsn_t lsn_from;
 | 
			
		||||
 | 
			
		||||
	char buffer[CDIO_CD_FRAMESIZE_RAW];
 | 
			
		||||
	int buffer_lsn;
 | 
			
		||||
	lsn_t buffer_lsn;
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
	CdioParanoiaInputStream(const char *_uri, Mutex &_mutex,
 | 
			
		||||
				cdrom_drive_t *_drv, CdIo_t *_cdio,
 | 
			
		||||
				bool reverse_endian,
 | 
			
		||||
				lsn_t _lsn_from, lsn_t _lsn_to)
 | 
			
		||||
				lsn_t _lsn_from, lsn_t lsn_to)
 | 
			
		||||
		:InputStream(_uri, _mutex),
 | 
			
		||||
		 drv(_drv), cdio(_cdio), para(drv),
 | 
			
		||||
		 lsn_from(_lsn_from), lsn_to(_lsn_to),
 | 
			
		||||
		 lsn_relofs(0),
 | 
			
		||||
		 lsn_from(_lsn_from),
 | 
			
		||||
		 buffer_lsn(-1)
 | 
			
		||||
	{
 | 
			
		||||
		/* Set reading mode for full paranoia, but allow
 | 
			
		||||
		   skipping sectors. */
 | 
			
		||||
		para.SetMode(PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP);
 | 
			
		||||
		para.SetMode(mode_flags);
 | 
			
		||||
 | 
			
		||||
		/* seek to beginning of the track */
 | 
			
		||||
		para.Seek(lsn_from);
 | 
			
		||||
@@ -98,11 +104,6 @@ class CdioParanoiaInputStream final : public InputStream {
 | 
			
		||||
	void Seek(std::unique_lock<Mutex> &lock, offset_type offset) override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static constexpr Domain cdio_domain("cdio");
 | 
			
		||||
 | 
			
		||||
static bool default_reverse_endian;
 | 
			
		||||
static unsigned speed = 0;
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
input_cdio_init(EventLoop &, const ConfigBlock &block)
 | 
			
		||||
{
 | 
			
		||||
@@ -117,6 +118,26 @@ input_cdio_init(EventLoop &, const ConfigBlock &block)
 | 
			
		||||
						 value);
 | 
			
		||||
	}
 | 
			
		||||
	speed = block.GetBlockValue("speed",0U);
 | 
			
		||||
 | 
			
		||||
	if (const auto *param = block.GetBlockParam("mode")) {
 | 
			
		||||
		param->With([](const char *s){
 | 
			
		||||
			if (StringIsEqual(s, "disable"))
 | 
			
		||||
				mode_flags = PARANOIA_MODE_DISABLE;
 | 
			
		||||
			else if (StringIsEqual(s, "overlap"))
 | 
			
		||||
				mode_flags = PARANOIA_MODE_OVERLAP;
 | 
			
		||||
			else if (StringIsEqual(s, "full"))
 | 
			
		||||
				mode_flags = PARANOIA_MODE_FULL;
 | 
			
		||||
			else
 | 
			
		||||
				throw std::invalid_argument{"Invalid paranoia mode"};
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (const auto *param = block.GetBlockParam("skip")) {
 | 
			
		||||
		if (param->GetBoolValue())
 | 
			
		||||
			mode_flags &= ~PARANOIA_MODE_NEVERSKIP;
 | 
			
		||||
		else
 | 
			
		||||
			mode_flags |= PARANOIA_MODE_NEVERSKIP;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct CdioUri {
 | 
			
		||||
@@ -173,9 +194,12 @@ cdio_detect_device()
 | 
			
		||||
	if (devices == nullptr)
 | 
			
		||||
		return nullptr;
 | 
			
		||||
 | 
			
		||||
	AllocatedPath path = AllocatedPath::FromFS(devices[0]);
 | 
			
		||||
	cdio_free_device_list(devices);
 | 
			
		||||
	return path;
 | 
			
		||||
	AtScopeExit(devices) { cdio_free_device_list(devices); };
 | 
			
		||||
 | 
			
		||||
	if (devices[0] == nullptr)
 | 
			
		||||
		return nullptr;
 | 
			
		||||
 | 
			
		||||
	return AllocatedPath::FromFS(devices[0]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static InputStreamPtr
 | 
			
		||||
@@ -271,81 +295,70 @@ CdioParanoiaInputStream::Seek(std::unique_lock<Mutex> &,
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	/* calculate current LSN */
 | 
			
		||||
	lsn_relofs = new_offset / CDIO_CD_FRAMESIZE_RAW;
 | 
			
		||||
	offset = new_offset;
 | 
			
		||||
	const lsn_t lsn_relofs = new_offset / CDIO_CD_FRAMESIZE_RAW;
 | 
			
		||||
 | 
			
		||||
	{
 | 
			
		||||
	if (lsn_relofs != buffer_lsn) {
 | 
			
		||||
		const ScopeUnlock unlock(mutex);
 | 
			
		||||
		para.Seek(lsn_from + lsn_relofs);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	offset = new_offset;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t
 | 
			
		||||
CdioParanoiaInputStream::Read(std::unique_lock<Mutex> &,
 | 
			
		||||
			      void *ptr, size_t length)
 | 
			
		||||
{
 | 
			
		||||
	size_t nbytes = 0;
 | 
			
		||||
	char *wptr = (char *) ptr;
 | 
			
		||||
	/* end of track ? */
 | 
			
		||||
	if (IsEOF())
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
	while (length > 0) {
 | 
			
		||||
		/* end of track ? */
 | 
			
		||||
		if (lsn_from + lsn_relofs > lsn_to)
 | 
			
		||||
			break;
 | 
			
		||||
	//current sector was changed ?
 | 
			
		||||
	const int16_t *rbuf;
 | 
			
		||||
 | 
			
		||||
		//current sector was changed ?
 | 
			
		||||
		const int16_t *rbuf;
 | 
			
		||||
		if (lsn_relofs != buffer_lsn) {
 | 
			
		||||
			const ScopeUnlock unlock(mutex);
 | 
			
		||||
	const lsn_t lsn_relofs = offset / CDIO_CD_FRAMESIZE_RAW;
 | 
			
		||||
	const std::size_t diff = offset % CDIO_CD_FRAMESIZE_RAW;
 | 
			
		||||
 | 
			
		||||
			try {
 | 
			
		||||
				rbuf = para.Read().data;
 | 
			
		||||
			} catch (...) {
 | 
			
		||||
				char *s_err = cdio_cddap_errors(drv);
 | 
			
		||||
				if (s_err) {
 | 
			
		||||
					FmtError(cdio_domain,
 | 
			
		||||
						 "paranoia_read: {}", s_err);
 | 
			
		||||
					cdio_cddap_free_messages(s_err);
 | 
			
		||||
				}
 | 
			
		||||
	if (lsn_relofs != buffer_lsn) {
 | 
			
		||||
		const ScopeUnlock unlock(mutex);
 | 
			
		||||
 | 
			
		||||
				throw;
 | 
			
		||||
		try {
 | 
			
		||||
			rbuf = para.Read().data;
 | 
			
		||||
		} catch (...) {
 | 
			
		||||
			char *s_err = cdio_cddap_errors(drv);
 | 
			
		||||
			if (s_err) {
 | 
			
		||||
				FmtError(cdio_domain,
 | 
			
		||||
					 "paranoia_read: {}", s_err);
 | 
			
		||||
				cdio_cddap_free_messages(s_err);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			//store current buffer
 | 
			
		||||
			memcpy(buffer, rbuf, CDIO_CD_FRAMESIZE_RAW);
 | 
			
		||||
			buffer_lsn = lsn_relofs;
 | 
			
		||||
		} else {
 | 
			
		||||
			//use cached sector
 | 
			
		||||
			rbuf = (const int16_t *)buffer;
 | 
			
		||||
			throw;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		//correct offset
 | 
			
		||||
		const int diff = offset - lsn_relofs * CDIO_CD_FRAMESIZE_RAW;
 | 
			
		||||
 | 
			
		||||
		assert(diff >= 0 && diff < CDIO_CD_FRAMESIZE_RAW);
 | 
			
		||||
 | 
			
		||||
		const size_t maxwrite = CDIO_CD_FRAMESIZE_RAW - diff;  //# of bytes pending in current buffer
 | 
			
		||||
		const size_t len = (length < maxwrite? length : maxwrite);
 | 
			
		||||
 | 
			
		||||
		//skip diff bytes from this lsn
 | 
			
		||||
		memcpy(wptr, ((const char *)rbuf) + diff, len);
 | 
			
		||||
		//update pointer
 | 
			
		||||
		wptr += len;
 | 
			
		||||
		nbytes += len;
 | 
			
		||||
 | 
			
		||||
		//update offset
 | 
			
		||||
		offset += len;
 | 
			
		||||
		lsn_relofs = offset / CDIO_CD_FRAMESIZE_RAW;
 | 
			
		||||
		//update length
 | 
			
		||||
		length -= len;
 | 
			
		||||
		//store current buffer
 | 
			
		||||
		memcpy(buffer, rbuf, CDIO_CD_FRAMESIZE_RAW);
 | 
			
		||||
		buffer_lsn = lsn_relofs;
 | 
			
		||||
	} else {
 | 
			
		||||
		//use cached sector
 | 
			
		||||
		rbuf = (const int16_t *)buffer;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const size_t maxwrite = CDIO_CD_FRAMESIZE_RAW - diff;  //# of bytes pending in current buffer
 | 
			
		||||
	const std::size_t nbytes = std::min(length, maxwrite);
 | 
			
		||||
 | 
			
		||||
	//skip diff bytes from this lsn
 | 
			
		||||
	memcpy(ptr, ((const char *)rbuf) + diff, nbytes);
 | 
			
		||||
 | 
			
		||||
	//update offset
 | 
			
		||||
	offset += nbytes;
 | 
			
		||||
 | 
			
		||||
	return nbytes;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
CdioParanoiaInputStream::IsEOF() const noexcept
 | 
			
		||||
{
 | 
			
		||||
	return lsn_from + lsn_relofs > lsn_to;
 | 
			
		||||
	return offset >= size;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static constexpr const char *cdio_paranoia_prefixes[] = {
 | 
			
		||||
 
 | 
			
		||||
@@ -82,7 +82,7 @@ class CurlInputStream final : public AsyncInputStream, CurlResponseHandler {
 | 
			
		||||
public:
 | 
			
		||||
	template<typename I>
 | 
			
		||||
	CurlInputStream(EventLoop &event_loop, const char *_url,
 | 
			
		||||
			const std::multimap<std::string, std::string> &headers,
 | 
			
		||||
			const Curl::Headers &headers,
 | 
			
		||||
			I &&_icy,
 | 
			
		||||
			Mutex &_mutex);
 | 
			
		||||
 | 
			
		||||
@@ -92,7 +92,7 @@ public:
 | 
			
		||||
	CurlInputStream &operator=(const CurlInputStream &) = delete;
 | 
			
		||||
 | 
			
		||||
	static InputStreamPtr Open(const char *url,
 | 
			
		||||
				   const std::multimap<std::string, std::string> &headers,
 | 
			
		||||
				   const Curl::Headers &headers,
 | 
			
		||||
				   Mutex &mutex);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
@@ -131,8 +131,7 @@ private:
 | 
			
		||||
	void SeekInternal(offset_type new_offset);
 | 
			
		||||
 | 
			
		||||
	/* virtual methods from CurlResponseHandler */
 | 
			
		||||
	void OnHeaders(unsigned status,
 | 
			
		||||
		       std::multimap<std::string, std::string> &&headers) override;
 | 
			
		||||
	void OnHeaders(unsigned status, Curl::Headers &&headers) override;
 | 
			
		||||
	void OnData(ConstBuffer<void> data) override;
 | 
			
		||||
	void OnEnd() override;
 | 
			
		||||
	void OnError(std::exception_ptr e) noexcept override;
 | 
			
		||||
@@ -227,7 +226,7 @@ WithConvertedTagValue(const char *uri, const char *value, F &&f) noexcept
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
CurlInputStream::OnHeaders(unsigned status,
 | 
			
		||||
			   std::multimap<std::string, std::string> &&headers)
 | 
			
		||||
			   Curl::Headers &&headers)
 | 
			
		||||
{
 | 
			
		||||
	assert(GetEventLoop().IsInside());
 | 
			
		||||
	assert(!postponed_exception);
 | 
			
		||||
@@ -391,7 +390,7 @@ input_curl_finish() noexcept
 | 
			
		||||
template<typename I>
 | 
			
		||||
inline
 | 
			
		||||
CurlInputStream::CurlInputStream(EventLoop &event_loop, const char *_url,
 | 
			
		||||
				 const std::multimap<std::string, std::string> &headers,
 | 
			
		||||
				 const Curl::Headers &headers,
 | 
			
		||||
				 I &&_icy,
 | 
			
		||||
				 Mutex &_mutex)
 | 
			
		||||
	:AsyncInputStream(event_loop, _url, _mutex,
 | 
			
		||||
@@ -418,7 +417,6 @@ CurlInputStream::InitEasy()
 | 
			
		||||
	request->SetOption(CURLOPT_HTTP200ALIASES, http_200_aliases);
 | 
			
		||||
	request->SetOption(CURLOPT_FOLLOWLOCATION, 1L);
 | 
			
		||||
	request->SetOption(CURLOPT_MAXREDIRS, 5L);
 | 
			
		||||
	request->SetOption(CURLOPT_FAILONERROR, 1L);
 | 
			
		||||
 | 
			
		||||
	/* this option eliminates the probe request when
 | 
			
		||||
	   username/password are specified */
 | 
			
		||||
@@ -440,6 +438,14 @@ CurlInputStream::InitEasy()
 | 
			
		||||
	request->SetVerifyPeer(verify_peer);
 | 
			
		||||
	request->SetVerifyHost(verify_host);
 | 
			
		||||
	request->SetOption(CURLOPT_HTTPHEADER, request_headers.Get());
 | 
			
		||||
 | 
			
		||||
	try {
 | 
			
		||||
		request->SetProxyVerifyPeer(verify_peer);
 | 
			
		||||
		request->SetProxyVerifyHost(verify_host);
 | 
			
		||||
	} catch (...) {
 | 
			
		||||
		/* these methods fail if libCURL was compiled with
 | 
			
		||||
		   CURL_DISABLE_PROXY; ignore silently */
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
@@ -491,7 +497,7 @@ CurlInputStream::DoSeek(offset_type new_offset)
 | 
			
		||||
 | 
			
		||||
inline InputStreamPtr
 | 
			
		||||
CurlInputStream::Open(const char *url,
 | 
			
		||||
		      const std::multimap<std::string, std::string> &headers,
 | 
			
		||||
		      const Curl::Headers &headers,
 | 
			
		||||
		      Mutex &mutex)
 | 
			
		||||
{
 | 
			
		||||
	auto icy = std::make_shared<IcyMetaDataParser>();
 | 
			
		||||
@@ -510,8 +516,7 @@ CurlInputStream::Open(const char *url,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
InputStreamPtr
 | 
			
		||||
OpenCurlInputStream(const char *uri,
 | 
			
		||||
		    const std::multimap<std::string, std::string> &headers,
 | 
			
		||||
OpenCurlInputStream(const char *uri, const Curl::Headers &headers,
 | 
			
		||||
		    Mutex &mutex)
 | 
			
		||||
{
 | 
			
		||||
	return CurlInputStream::Open(uri, headers, mutex);
 | 
			
		||||
 
 | 
			
		||||
@@ -20,12 +20,10 @@
 | 
			
		||||
#ifndef MPD_INPUT_CURL_HXX
 | 
			
		||||
#define MPD_INPUT_CURL_HXX
 | 
			
		||||
 | 
			
		||||
#include "lib/curl/Headers.hxx"
 | 
			
		||||
#include "input/Ptr.hxx"
 | 
			
		||||
#include "thread/Mutex.hxx"
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <map>
 | 
			
		||||
 | 
			
		||||
extern const struct InputPlugin input_plugin_curl;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -36,8 +34,7 @@ extern const struct InputPlugin input_plugin_curl;
 | 
			
		||||
 * Throws on error.
 | 
			
		||||
 */
 | 
			
		||||
InputStreamPtr
 | 
			
		||||
OpenCurlInputStream(const char *uri,
 | 
			
		||||
		    const std::multimap<std::string, std::string> &headers,
 | 
			
		||||
OpenCurlInputStream(const char *uri, const Curl::Headers &headers,
 | 
			
		||||
		    Mutex &mutex);
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -164,7 +164,7 @@ QobuzClient::InvokeHandlers() noexcept
 | 
			
		||||
 | 
			
		||||
std::string
 | 
			
		||||
QobuzClient::MakeUrl(const char *object, const char *method,
 | 
			
		||||
		     const std::multimap<std::string, std::string> &query) const noexcept
 | 
			
		||||
		     const Curl::Headers &query) const noexcept
 | 
			
		||||
{
 | 
			
		||||
	assert(!query.empty());
 | 
			
		||||
 | 
			
		||||
@@ -183,7 +183,7 @@ QobuzClient::MakeUrl(const char *object, const char *method,
 | 
			
		||||
 | 
			
		||||
std::string
 | 
			
		||||
QobuzClient::MakeSignedUrl(const char *object, const char *method,
 | 
			
		||||
			   const std::multimap<std::string, std::string> &query) const noexcept
 | 
			
		||||
			   const Curl::Headers &query) const noexcept
 | 
			
		||||
{
 | 
			
		||||
	assert(!query.empty());
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -23,12 +23,12 @@
 | 
			
		||||
#include "QobuzSession.hxx"
 | 
			
		||||
#include "QobuzLoginRequest.hxx"
 | 
			
		||||
#include "lib/curl/Init.hxx"
 | 
			
		||||
#include "lib/curl/Headers.hxx"
 | 
			
		||||
#include "thread/Mutex.hxx"
 | 
			
		||||
#include "event/DeferEvent.hxx"
 | 
			
		||||
#include "util/IntrusiveList.hxx"
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <map>
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
class QobuzSessionHandler
 | 
			
		||||
@@ -94,10 +94,10 @@ public:
 | 
			
		||||
	QobuzSession GetSession() const;
 | 
			
		||||
 | 
			
		||||
	std::string MakeUrl(const char *object, const char *method,
 | 
			
		||||
			    const std::multimap<std::string, std::string> &query) const noexcept;
 | 
			
		||||
			    const Curl::Headers &query) const noexcept;
 | 
			
		||||
 | 
			
		||||
	std::string MakeSignedUrl(const char *object, const char *method,
 | 
			
		||||
				  const std::multimap<std::string, std::string> &query) const noexcept;
 | 
			
		||||
				  const Curl::Headers &query) const noexcept;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	void StartLogin();
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user