Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/platformdirs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,13 @@ def user_videos_dir() -> str:
return PlatformDirs().user_videos_dir


def user_music_dir() -> str:
"""
:returns: music directory tied to the user
"""
return PlatformDirs().user_music_dir


def user_runtime_dir(
appname: str | None = None,
appauthor: str | None | Literal[False] = None,
Expand Down Expand Up @@ -494,6 +501,13 @@ def user_videos_path() -> Path:
return PlatformDirs().user_videos_path


def user_music_path() -> Path:
"""
:returns: music path tied to the user
"""
return PlatformDirs().user_music_path


def user_runtime_path(
appname: str | None = None,
appauthor: str | None | Literal[False] = None,
Expand Down Expand Up @@ -532,6 +546,7 @@ def user_runtime_path(
"user_documents_dir",
"user_pictures_dir",
"user_videos_dir",
"user_music_dir",
"user_runtime_dir",
"site_data_dir",
"site_config_dir",
Expand All @@ -544,6 +559,7 @@ def user_runtime_path(
"user_documents_path",
"user_pictures_path",
"user_videos_path",
"user_music_path",
"user_runtime_path",
"site_data_path",
"site_config_path",
Expand Down
1 change: 1 addition & 0 deletions src/platformdirs/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"user_documents_dir",
"user_pictures_dir",
"user_videos_dir",
"user_music_dir",
"user_runtime_dir",
"site_data_dir",
"site_config_dir",
Expand Down
23 changes: 23 additions & 0 deletions src/platformdirs/android.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ def user_videos_dir(self) -> str:
"""
return _android_videos_folder()

@property
def user_music_dir(self) -> str:
"""
:return: music directory tied to the user e.g. ``/storage/emulated/0/Music``
"""
return _android_music_folder()

@property
def user_runtime_dir(self) -> str:
"""
Expand Down Expand Up @@ -167,6 +174,22 @@ def _android_videos_folder() -> str:
return videos_dir


@lru_cache(maxsize=1)
def _android_music_folder() -> str:
""":return: music folder for the Android OS"""
# Get directories with pyjnius
try:
from jnius import autoclass

Context = autoclass("android.content.Context") # noqa: N806
Environment = autoclass("android.os.Environment") # noqa: N806
music_dir: str = Context.getExternalFilesDir(Environment.DIRECTORY_MUSIC).getAbsolutePath()
except Exception:
music_dir = "/storage/emulated/0/Music"

return music_dir


__all__ = [
"Android",
]
10 changes: 10 additions & 0 deletions src/platformdirs/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,11 @@ def user_pictures_dir(self) -> str:
def user_videos_dir(self) -> str:
""":return: videos directory tied to the user"""

@property
@abstractmethod
def user_music_dir(self) -> str:
""":return: music directory tied to the user"""

@property
@abstractmethod
def user_runtime_dir(self) -> str:
Expand Down Expand Up @@ -193,6 +198,11 @@ def user_videos_path(self) -> Path:
""":return: videos path tied to the user"""
return Path(self.user_videos_dir)

@property
def user_music_path(self) -> Path:
""":return: music path tied to the user"""
return Path(self.user_music_dir)

@property
def user_runtime_path(self) -> Path:
""":return: runtime path tied to the user"""
Expand Down
5 changes: 5 additions & 0 deletions src/platformdirs/macos.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ def user_videos_dir(self) -> str:
""":return: videos directory tied to the user, e.g. ``~/Movies``"""
return os.path.expanduser("~/Movies")

@property
def user_music_dir(self) -> str:
""":return: music directory tied to the user, e.g. ``~/Music``"""
return os.path.expanduser("~/Music")

@property
def user_runtime_dir(self) -> str:
""":return: runtime directory tied to the user, e.g. ``~/Library/Caches/TemporaryItems/$appname/$version``"""
Expand Down
7 changes: 7 additions & 0 deletions src/platformdirs/unix.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,13 @@ def user_videos_dir(self) -> str:
"""
return _get_user_media_dir("XDG_VIDEOS_DIR", "~/Videos")

@property
def user_music_dir(self) -> str:
"""
:return: music directory tied to the user, e.g. ``~/Music``
"""
return _get_user_media_dir("XDG_MUSIC_DIR", "~/Music")

@property
def user_runtime_dir(self) -> str:
"""
Expand Down
36 changes: 28 additions & 8 deletions src/platformdirs/windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,13 @@ def user_videos_dir(self) -> str:
"""
return os.path.normpath(get_win_folder("CSIDL_MYVIDEO"))

@property
def user_music_dir(self) -> str:
"""
:return: music directory tied to the user e.g. ``%USERPROFILE%\\Music``
"""
return os.path.normpath(get_win_folder("CSIDL_MYMUSIC"))

@property
def user_runtime_dir(self) -> str:
"""
Expand All @@ -127,14 +134,9 @@ def user_runtime_dir(self) -> str:

def get_win_folder_from_env_vars(csidl_name: str) -> str:
"""Get folder from environment variables."""
if csidl_name == "CSIDL_PERSONAL": # does not have an environment name
return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Documents")

if csidl_name == "CSIDL_MYPICTURES": # does not have an environment name
return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Pictures")

if csidl_name == "CSIDL_MYVIDEO": # does not have an environment name
return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Videos")
result = get_win_folder_if_csidl_name_not_env_var(csidl_name)
if result is not None:
return result

env_var_name = {
"CSIDL_APPDATA": "APPDATA",
Expand All @@ -149,6 +151,22 @@ def get_win_folder_from_env_vars(csidl_name: str) -> str:
return result


def get_win_folder_if_csidl_name_not_env_var(csidl_name: str) -> str | None:
"""Get folder for a CSIDL name that does not exist as an environment variable."""
if csidl_name == "CSIDL_PERSONAL":
return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Documents")

if csidl_name == "CSIDL_MYPICTURES":
return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Pictures")

if csidl_name == "CSIDL_MYVIDEO":
return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Videos")

if csidl_name == "CSIDL_MYMUSIC":
return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Music")
return None


def get_win_folder_from_registry(csidl_name: str) -> str:
"""Get folder from the registry.

Expand All @@ -163,6 +181,7 @@ def get_win_folder_from_registry(csidl_name: str) -> str:
"CSIDL_PERSONAL": "Personal",
"CSIDL_MYPICTURES": "My Pictures",
"CSIDL_MYVIDEO": "My Video",
"CSIDL_MYMUSIC": "My Music",
}.get(csidl_name)
if shell_folder_name is None:
raise ValueError(f"Unknown CSIDL name: {csidl_name}")
Expand All @@ -184,6 +203,7 @@ def get_win_folder_via_ctypes(csidl_name: str) -> str:
"CSIDL_PERSONAL": 5,
"CSIDL_MYPICTURES": 39,
"CSIDL_MYVIDEO": 14,
"CSIDL_MYMUSIC": 13,
}.get(csidl_name)
if csidl_const is None:
raise ValueError(f"Unknown CSIDL name: {csidl_name}")
Expand Down
1 change: 1 addition & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"user_documents_dir",
"user_pictures_dir",
"user_videos_dir",
"user_music_dir",
"user_runtime_dir",
"site_data_dir",
"site_config_dir",
Expand Down
1 change: 1 addition & 0 deletions tests/test_android.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ def test_android(mocker: MockerFixture, params: dict[str, Any], func: str) -> No
"user_documents_dir": "/storage/emulated/0/Documents",
"user_pictures_dir": "/storage/emulated/0/Pictures",
"user_videos_dir": "/storage/emulated/0/DCIM/Camera",
"user_music_dir": "/storage/emulated/0/Music",
"user_runtime_dir": f"/data/data/com.example/cache{suffix}{'' if not params.get('opinion', True) else '/tmp'}",
}
expected = expected_map[func]
Expand Down
1 change: 1 addition & 0 deletions tests/test_macos.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def test_macos(params: dict[str, Any], func: str) -> None:
"user_documents_dir": f"{home}/Documents",
"user_pictures_dir": f"{home}/Pictures",
"user_videos_dir": f"{home}/Movies",
"user_music_dir": f"{home}/Music",
"user_runtime_dir": f"{home}/Library/Caches/TemporaryItems{suffix}",
}
expected = expected_map[func]
Expand Down
4 changes: 3 additions & 1 deletion tests/test_unix.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from platformdirs.unix import Unix


@pytest.mark.parametrize("prop", ["user_documents_dir", "user_pictures_dir", "user_videos_dir"])
@pytest.mark.parametrize("prop", ["user_documents_dir", "user_pictures_dir", "user_videos_dir", "user_music_dir"])
def test_user_media_dir(mocker: MockerFixture, prop: str) -> None:
example_path = "/home/example/ExampleMediaFolder"
mock = mocker.patch("platformdirs.unix._get_user_dirs_folder")
Expand All @@ -27,6 +27,7 @@ def test_user_media_dir(mocker: MockerFixture, prop: str) -> None:
pytest.param("XDG_DOCUMENTS_DIR", "user_documents_dir", id="user_documents_dir"),
pytest.param("XDG_PICTURES_DIR", "user_pictures_dir", id="user_pictures_dir"),
pytest.param("XDG_VIDEOS_DIR", "user_videos_dir", id="user_videos_dir"),
pytest.param("XDG_MUSIC_DIR", "user_music_dir", id="user_music_dir"),
],
)
def test_user_media_dir_env_var(mocker: MockerFixture, env_var: str, prop: str) -> None:
Expand All @@ -46,6 +47,7 @@ def test_user_media_dir_env_var(mocker: MockerFixture, env_var: str, prop: str)
pytest.param("XDG_DOCUMENTS_DIR", "user_documents_dir", "/home/example/Documents", id="user_documents_dir"),
pytest.param("XDG_PICTURES_DIR", "user_pictures_dir", "/home/example/Pictures", id="user_pictures_dir"),
pytest.param("XDG_VIDEOS_DIR", "user_videos_dir", "/home/example/Videos", id="user_videos_dir"),
pytest.param("XDG_MUSIC_DIR", "user_music_dir", "/home/example/Music", id="user_music_dir"),
],
)
def test_user_media_dir_default(mocker: MockerFixture, env_var: str, prop: str, default_abs_path: str) -> None:
Expand Down