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
93 changes: 55 additions & 38 deletions test/trackpack_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import os
import unittest
from pathlib import Path
from unittest.mock import call, patch

from trackpack.trackpacker import MissingFileException, TrackPacker
Expand All @@ -25,6 +25,8 @@
class TestTrackPack(unittest.TestCase):
@patch("os.walk")
def test_discover_audiofiles_returns_audio_files(self, walk_mock) -> None:
export_dir = Path("/tmp/export")

walk_mock.return_value = _create_walk_files(
[
"proj stem2.wav",
Expand All @@ -35,13 +37,13 @@ def test_discover_audiofiles_returns_audio_files(self, walk_mock) -> None:
]
)

trackpacker = TrackPacker("proj", "/tmp/export")
trackpacker = TrackPacker("proj", str(export_dir))
(master, stems) = trackpacker.discover_audiofiles()
walk_mock.assert_called_with("/tmp/export")
walk_mock.assert_called_with(str(export_dir))
self.assertEqual("proj.wav", master)
self.assertListEqual(
_files_in_dir(
"/tmp/export",
export_dir,
[
"proj stem2.wav",
"proj stem4.wav",
Expand All @@ -56,6 +58,7 @@ def test_discover_audiofiles_returns_audio_files(self, walk_mock) -> None:
def test_discover_audiofiles_returns_only_related_audio_files(
self, walk_mock
) -> None:
export_dir = Path("/tmp/export")
walk_mock.return_value = _create_walk_files(
[
"proj stem2.wav",
Expand All @@ -67,16 +70,17 @@ def test_discover_audiofiles_returns_only_related_audio_files(
]
)

trackpacker = TrackPacker("proj", "/tmp/export")
trackpacker = TrackPacker("proj", str(export_dir))
(_, stems) = trackpacker.discover_audiofiles()
self.assertListEqual(
_files_in_dir("/tmp/export/", ["proj stem2.wav", "proj stem1.wav"]), stems
_files_in_dir(export_dir, ["proj stem2.wav", "proj stem1.wav"]), stems
)

@patch("os.walk")
def test_discover_audiofiles_master_track_matches_project_name(
self, walk_mock
) -> None:
export_dir = Path("/tmp/export")
walk_mock.return_value = _create_walk_files(
[
"example.wav",
Expand All @@ -86,32 +90,37 @@ def test_discover_audiofiles_master_track_matches_project_name(
"proj stem3.wav",
]
)
trackpacker = TrackPacker("example", "/tmp/export")
trackpacker = TrackPacker("example", str(export_dir))
(master, _) = trackpacker.discover_audiofiles()
self.assertEqual("example.wav", master)

@patch("os.walk")
def test_discover_audiofiles_fails_if_no_master(self, walk_mock) -> None:
export_dir = Path("/tmp/export")
walk_mock.return_value = _create_walk_files(
["proj stem1.wav", "proj stem2.wav"]
)

with self.assertRaises(MissingFileException):
trackpacker = TrackPacker("proj", "/tmp/export")
trackpacker = TrackPacker("proj", str(export_dir))
trackpacker.discover_audiofiles()

@patch("os.walk")
def test_discover_audiofiles_fails_if_no_stems(self, walk_mock) -> None:
export_dir = Path("/tmp/export")
walk_mock.return_value = _create_walk_files(["proj.wav"])

with self.assertRaises(MissingFileException):
trackpacker = TrackPacker("proj", "/tmp/export")
trackpacker = TrackPacker("proj", str(export_dir))
trackpacker.discover_audiofiles()

@patch("os.walk")
def test_discover_audiofiles_returns_explicit_passed_audio_files(
self, walk_mock
) -> None:
export_dir = Path("/tmp/export")
temp_dir = Path("/tmp/x")

walk_mock.return_value = _create_walk_files(
[
"proj stem2.wav",
Expand All @@ -122,60 +131,68 @@ def test_discover_audiofiles_returns_explicit_passed_audio_files(
]
)

trackpacker = TrackPacker("proj", "/tmp/export")
(master, stems) = trackpacker.discover_audiofiles(
_files_in_dir(
"/tmp/export", ["/tmp/x/proj stem1.wav", "/tmp/x/proj stem3.wav"]
)
)
walk_mock.assert_called_with("/tmp/export")
trackpacker = TrackPacker("proj", str(export_dir))

explicit_files_list = ["/tmp/x/proj stem1.wav", "/tmp/x/proj stem3.wav"]

(master, stems) = trackpacker.discover_audiofiles(explicit_files_list)
walk_mock.assert_called_with(str(export_dir))
self.assertEqual("proj.wav", master)
self.assertListEqual(
_files_in_dir("/tmp/x/", ["proj stem1.wav", "proj stem3.wav"]), stems
_files_in_dir(temp_dir, ["proj stem1.wav", "proj stem3.wav"]), stems
)

@patch("trackpack.trackpacker.ZipFile", autospec=True)
def test_pack_files_creates_archive_of_stems(self, zip_mock) -> None:
trackpacker = TrackPacker("projname", "/tmp/proj/Export")
export_dir = Path("/tmp/proj/Export")
trackpacker = TrackPacker("projname", str(export_dir))

files_to_pack = _files_in_dir(export_dir, ["a.wav", "b.wav", "c.wav"])

trackpacker.pack_files(
"archivename",
_files_in_dir("/tmp/proj/Export", ["a.wav", "b.wav", "c.wav"]),
files_to_pack,
)
zip_mock.assert_has_calls(
_create_zip_mock_calls(
"archivename",
"/tmp/proj/Export",
str(export_dir),
{"a.wav": "a.wav", "b.wav": "b.wav", "c.wav": "c.wav"},
)
)

@patch("trackpack.trackpacker.ZipFile", autospec=True)
def test_pack_files_removes_project_name_from_stems(self, zip_mock) -> None:
trackpacker = TrackPacker("proj1", "/tmp/x")
trackpacker.pack_files(
"archive1", _files_in_dir("/tmp/x", ["proj1 a.wav", "b.wav", "proj1 c.wav"])
export_dir = Path("/tmp/x")
trackpacker = TrackPacker("proj1", str(export_dir))

files_to_pack = _files_in_dir(
export_dir, ["proj1 a.wav", "b.wav", "proj1 c.wav"]
)

trackpacker.pack_files("archive1", files_to_pack)
zip_mock.assert_has_calls(
_create_zip_mock_calls(
"archive1",
"/tmp/x",
str(export_dir),
{"proj1 a.wav": "a.wav", "b.wav": "b.wav", "proj1 c.wav": "c.wav"},
)
)

@patch("trackpack.trackpacker.ZipFile", autospec=True)
def test_pack_files_replaces_blanks_in_names(self, zip_mock) -> None:
trackpacker = TrackPacker("proj1", "/tmp/st u v w")
trackpacker.pack_files(
"archive1",
_files_in_dir(
"/tmp/st u v w", ["proj1 a a a.wav", "b 123.wav", "proj1 cd efg.wav"]
),
export_dir = Path("/tmp/st u v w")
trackpacker = TrackPacker("proj1", str(export_dir))

files_to_pack = _files_in_dir(
export_dir, ["proj1 a a a.wav", "b 123.wav", "proj1 cd efg.wav"]
)

trackpacker.pack_files("archive1", files_to_pack)
zip_mock.assert_has_calls(
_create_zip_mock_calls(
"archive1",
"/tmp/st u v w",
str(export_dir),
{
"proj1 a a a.wav": "a-a-a.wav",
"b 123.wav": "b-123.wav",
Expand All @@ -185,8 +202,8 @@ def test_pack_files_replaces_blanks_in_names(self, zip_mock) -> None:
)


def _files_in_dir(dirpath: str, filenames: list[str]) -> list[str]:
return [os.path.join(dirpath, file) for file in filenames]
def _files_in_dir(dirpath: Path, filenames: list[str]) -> list[Path]:
return [Path(dirpath) / file for file in filenames]


def _create_walk_files(files: list[str]):
Expand All @@ -196,16 +213,16 @@ def _create_walk_files(files: list[str]):
def _create_zip_mock_calls(
archive_name: str, proj_export_dir: str, files: dict[str, str]
):
proj_export_dir_path = Path(proj_export_dir)

call_list = [
call(os.path.join(proj_export_dir, f"{archive_name}.zip"), "w"),
call(proj_export_dir_path / f"{archive_name}.zip", "w"),
call().__enter__(), # pylint: disable=unnecessary-dunder-call
]

for name, entry in files.items():
file_path = proj_export_dir_path / name
# pylint: disable=unnecessary-dunder-call
call_list.append(
call().__enter__().write(os.path.join(proj_export_dir, name), entry)
)
call_list.append(call().__enter__().write(file_path, entry))
call_list.append(call().__exit__(None, None, None))

return call_list
8 changes: 5 additions & 3 deletions trackpack/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import sys
from pathlib import Path

from trackpack import cli
from trackpack.config import Config
Expand All @@ -28,9 +29,10 @@ def __fail(msg: str) -> None:


def __read_config(filename: str) -> Config:
with open(filename, "r", encoding="utf-8") as config_file:
config_path = Path(filename)
with config_path.open("r", encoding="utf-8") as config_file:
cfg = Config()
cfg.load_from_yaml(config_file)
cfg.load_from_yaml(config_file.read())
return cfg


Expand All @@ -46,7 +48,7 @@ def main() -> None:
(_, stems) = trackpacker.discover_audiofiles(args.pack_explicit_files)
trackpacker.pack_files(cfg.archive_name, stems)
except FileNotFoundError as ex:
__fail(f"{ex.strerror}: {ex.filename}")
__fail(f"Configuration or file not found: '{ex.filename}'")


if __name__ == "__main__":
Expand Down
14 changes: 9 additions & 5 deletions trackpack/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def __init__(self) -> None:
def archive_name(self) -> str:
if self.append_date:
today_date = datetime.now(timezone.utc).date()
return "-".join((self._archive_name, today_date.strftime("%Y-%m-%d")))
return f"{self._archive_name}-{today_date.strftime('%Y-%m-%d')}"
return self._archive_name

@archive_name.setter
Expand All @@ -41,10 +41,14 @@ def archive_name(self, archive_name: str) -> None:
else:
self._archive_name = archive_name

def load_from_yaml(self, yaml_content) -> None:
config = yaml.safe_load(yaml_content)
self.name = config["name"]
self.archive_name = config.get("archive_name", self.name)
def load_from_yaml(self, yaml_content: str) -> None:
try:
config = yaml.safe_load(yaml_content)
except yaml.YAMLError as e:
raise ValueError(f"Error parsing YAML content: {e}") from e

self.name = config.get("name", "unnamed")
self._archive_name = config.get("archive_name", self.name)
self.append_date = config.get("append_date", False)

def load_from_cli_args(self, cli_args) -> None:
Expand Down
33 changes: 16 additions & 17 deletions trackpack/trackpacker.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import os
from pathlib import Path
from zipfile import ZipFile


Expand All @@ -25,37 +26,35 @@ class MissingFileException(Exception):

class TrackPacker:
def __init__(self, project_name: str, export_dir: str) -> None:
self.__project_name = project_name
self.__export_dir = export_dir
self._project_name = project_name
self._export_dir = Path(export_dir)

def discover_audiofiles(self, explicit_files: list[str] | None = None):
(_, _, filenames) = next(os.walk(self.__export_dir))
master = f"{self.__project_name}.wav"
files = list(filter(lambda f: f.endswith(".wav"), filenames))
filenames = [f for _, _, files in os.walk(str(self._export_dir)) for f in files]

master = f"{self._project_name}.wav"
files = [f for f in filenames if f.endswith(".wav")]

if master not in files:
raise MissingFileException("Master track not found")
files.remove(master)

if explicit_files:
files = [os.path.abspath(file) for file in explicit_files]
files = [Path(file).resolve() for file in explicit_files]
else:
files = [
os.path.abspath(os.path.join(self.__export_dir, file)) for file in files
]
files = [self._export_dir / f for f in files]

if not files:
raise MissingFileException("No stems found")

return (master, [os.path.abspath(file) for file in files])
return (master, [f.resolve() for f in files])

def pack_files(self, archive_name: str, files: list[str]):
with ZipFile(
f"{os.path.join(self.__export_dir, archive_name)}.zip", "w"
) as archive:
def pack_files(self, archive_name: str, files: list[Path]):
archive_path = self._export_dir / f"{archive_name}.zip"
with ZipFile(archive_path, "w") as archive:
for file in files:
archive.write(file, self.__normalize_stem_name(os.path.basename(file)))
archive.write(file, self._normalize_stem_name(file.name))

def __normalize_stem_name(self, stem_name: str) -> str:
stem_name = stem_name.removeprefix(self.__project_name)
def _normalize_stem_name(self, stem_name: str) -> str:
stem_name = stem_name.removeprefix(self._project_name)
return stem_name.strip().replace(" ", "-")
Loading