puremagic 2.2.0__tar.gz → 2.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. puremagic-2.2.1/.github/workflows/master-source-guard.yml +27 -0
  2. puremagic-2.2.1/.github/workflows/pythonpublish.yml +69 -0
  3. {puremagic-2.2.0 → puremagic-2.2.1}/.github/workflows/tests.yml +7 -6
  4. {puremagic-2.2.0 → puremagic-2.2.1}/.pre-commit-config.yaml +4 -4
  5. {puremagic-2.2.0 → puremagic-2.2.1}/CHANGELOG.md +13 -0
  6. {puremagic-2.2.0 → puremagic-2.2.1}/PKG-INFO +2 -1
  7. {puremagic-2.2.0 → puremagic-2.2.1}/puremagic/__init__.py +1 -2
  8. {puremagic-2.2.0 → puremagic-2.2.1}/puremagic/magic_data.json +16 -3
  9. {puremagic-2.2.0 → puremagic-2.2.1}/puremagic/main.py +36 -40
  10. {puremagic-2.2.0 → puremagic-2.2.1}/puremagic/scanners/cfbf_scanner.py +2 -3
  11. {puremagic-2.2.0 → puremagic-2.2.1}/puremagic/scanners/mpeg_audio_scanner.py +41 -47
  12. {puremagic-2.2.0 → puremagic-2.2.1}/puremagic/scanners/python_scanner.py +2 -3
  13. {puremagic-2.2.0 → puremagic-2.2.1}/puremagic/scanners/sndhdr_scanner.py +2 -3
  14. {puremagic-2.2.0 → puremagic-2.2.1}/puremagic/scanners/text_scanner.py +23 -10
  15. {puremagic-2.2.0 → puremagic-2.2.1}/puremagic/scanners/zip_scanner.py +12 -1
  16. {puremagic-2.2.0 → puremagic-2.2.1}/puremagic.egg-info/PKG-INFO +2 -1
  17. {puremagic-2.2.0 → puremagic-2.2.1}/puremagic.egg-info/SOURCES.txt +5 -0
  18. {puremagic-2.2.0 → puremagic-2.2.1}/pyproject.toml +20 -12
  19. puremagic-2.2.1/requirements-test.txt +3 -0
  20. {puremagic-2.2.0 → puremagic-2.2.1}/scripts/parse_ftk_kessler_sigs.py +1 -1
  21. puremagic-2.2.1/test/resources/office/test_html.xls +62 -0
  22. {puremagic-2.2.0 → puremagic-2.2.1}/test/test_common_extensions.py +47 -9
  23. puremagic-2.2.1/test/test_csv_quotes.py +72 -0
  24. puremagic-2.2.1/test/test_ftyp.py +39 -0
  25. puremagic-2.2.1/test/test_odf_templates.py +33 -0
  26. {puremagic-2.2.0 → puremagic-2.2.1}/test/test_scanners.py +13 -4
  27. puremagic-2.2.1/uv.lock +981 -0
  28. puremagic-2.2.0/.github/workflows/pythonpublish.yml +0 -35
  29. puremagic-2.2.0/requirements-test.txt +0 -3
  30. puremagic-2.2.0/uv.lock +0 -882
  31. {puremagic-2.2.0 → puremagic-2.2.1}/.claude/skills/changelog.md +0 -0
  32. {puremagic-2.2.0 → puremagic-2.2.1}/.coveragerc +0 -0
  33. {puremagic-2.2.0 → puremagic-2.2.1}/.gitignore +0 -0
  34. {puremagic-2.2.0 → puremagic-2.2.1}/AUTHORS.rst +0 -0
  35. {puremagic-2.2.0 → puremagic-2.2.1}/LICENSE +0 -0
  36. {puremagic-2.2.0 → puremagic-2.2.1}/MANIFEST.in +0 -0
  37. {puremagic-2.2.0 → puremagic-2.2.1}/README.rst +0 -0
  38. {puremagic-2.2.0 → puremagic-2.2.1}/puremagic/__main__.py +0 -0
  39. {puremagic-2.2.0 → puremagic-2.2.1}/puremagic/py.typed +0 -0
  40. {puremagic-2.2.0 → puremagic-2.2.1}/puremagic/scanners/__init__.py +0 -0
  41. {puremagic-2.2.0 → puremagic-2.2.1}/puremagic/scanners/asf_scanner.py +0 -0
  42. {puremagic-2.2.0 → puremagic-2.2.1}/puremagic/scanners/ebml_scanner.py +0 -0
  43. {puremagic-2.2.0 → puremagic-2.2.1}/puremagic/scanners/hdf5_scanner.py +0 -0
  44. {puremagic-2.2.0 → puremagic-2.2.1}/puremagic/scanners/helpers.py +0 -0
  45. {puremagic-2.2.0 → puremagic-2.2.1}/puremagic/scanners/json_scanner.py +1 -1
  46. {puremagic-2.2.0 → puremagic-2.2.1}/puremagic/scanners/ogg_scanner.py +0 -0
  47. {puremagic-2.2.0 → puremagic-2.2.1}/puremagic/scanners/pdf_scanner.py +0 -0
  48. {puremagic-2.2.0 → puremagic-2.2.1}/puremagic.egg-info/dependency_links.txt +0 -0
  49. {puremagic-2.2.0 → puremagic-2.2.1}/puremagic.egg-info/entry_points.txt +0 -0
  50. {puremagic-2.2.0 → puremagic-2.2.1}/puremagic.egg-info/top_level.txt +0 -0
  51. {puremagic-2.2.0 → puremagic-2.2.1}/setup.cfg +0 -0
  52. {puremagic-2.2.0 → puremagic-2.2.1}/test/__init__.py +0 -0
  53. {puremagic-2.2.0 → puremagic-2.2.1}/test/common.py +0 -0
  54. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/archive/test.7z +0 -0
  55. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/archive/test.big_endian.pcapng +0 -0
  56. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/archive/test.bz2 +0 -0
  57. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/archive/test.cb7 +0 -0
  58. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/archive/test.cbt +0 -0
  59. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/archive/test.cbz +0 -0
  60. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/archive/test.fb2.zip +0 -0
  61. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/archive/test.fbz +0 -0
  62. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/archive/test.gz +0 -0
  63. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/archive/test.little_endian.pcapng +0 -0
  64. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/archive/test.pcapng +0 -0
  65. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/archive/test.rar +0 -0
  66. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/archive/test.tar +0 -0
  67. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/archive/test.txt.lz4 +0 -0
  68. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/archive/test.txt.zst +0 -0
  69. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/archive/test.xz +0 -0
  70. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/archive/test.zip +0 -0
  71. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/audio/test.aac +0 -0
  72. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/audio/test.aif +0 -0
  73. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/audio/test.flac +0 -0
  74. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/audio/test.mp3 +0 -0
  75. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/audio/test.oga +0 -0
  76. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/audio/test.ogg +0 -0
  77. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/audio/test.opus +0 -0
  78. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/audio/test.sndr +0 -0
  79. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/audio/test.wav +0 -0
  80. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/audio/test.wma +0 -0
  81. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/audio/test_mp3_vbr_info_128k_notags.mp3 +0 -0
  82. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/audio/test_mp3_vbr_xing_128k_3di_id3v1.mp3 +0 -0
  83. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/audio/test_mp3_vbr_xing_128k_apev1.mp3 +0 -0
  84. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/audio/test_mp3_vbr_xing_128k_apev1_id3v1.mp3 +0 -0
  85. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/audio/test_mp3_vbr_xing_128k_apev2_tagplus_id3v1.mp3 +0 -0
  86. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/audio/test_mp3_vbr_xing_128k_ext_id3v1.mp3 +0 -0
  87. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/audio/test_mp3_vbr_xing_128k_lyrics3v2_id3v1.mp3 +0 -0
  88. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/audio/test_mp3_vbr_xing_128k_notags.mp3 +0 -0
  89. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/audio/test_mp3_vbr_xing_128k_tagplus_apev2_id3v1.mp3 +0 -0
  90. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/audio/test_mp3_vbr_xing_128k_tagplus_id3v1.mp3 +0 -0
  91. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/audio/test_mpeg2_mp3_VBR_128k_id3v2_24.mp3 +0 -0
  92. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/fake_file +0 -0
  93. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/images/test.avif +0 -0
  94. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/images/test.bmp +0 -0
  95. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/images/test.gif +0 -0
  96. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/images/test.ico +0 -0
  97. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/images/test.jpg +0 -0
  98. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/images/test.png +0 -0
  99. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/images/test.psd +0 -0
  100. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/images/test.svg +0 -0
  101. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/images/test.tga +0 -0
  102. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/images/test.tif +0 -0
  103. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/images/test.webp +0 -0
  104. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/images/test.xcf +0 -0
  105. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/images/test_varriant.svg +0 -0
  106. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/images/test_varriant_2.svg +0 -0
  107. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/media/test (single).vmdk +0 -0
  108. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/media/test (split).vmdk +0 -0
  109. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/media/test.iso +0 -0
  110. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/media/test.mdf +0 -0
  111. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/media/test.swf +0 -0
  112. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/office/test.doc +0 -0
  113. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/office/test.docm +0 -0
  114. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/office/test.docx +0 -0
  115. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/office/test.dotm +0 -0
  116. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/office/test.dotx +0 -0
  117. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/office/test.eml +0 -0
  118. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/office/test.fb2 +0 -0
  119. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/office/test.msg +0 -0
  120. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/office/test.odp +0 -0
  121. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/office/test.ods +0 -0
  122. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/office/test.odt +0 -0
  123. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/office/test.pdf +0 -0
  124. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/office/test.potm +0 -0
  125. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/office/test.potx +0 -0
  126. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/office/test.ppt +0 -0
  127. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/office/test.pptm +0 -0
  128. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/office/test.pptx +0 -0
  129. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/office/test.txt +0 -0
  130. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/office/test.xls +0 -0
  131. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/office/test.xlsb +0 -0
  132. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/office/test.xlsm +0 -0
  133. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/office/test.xlsx +0 -0
  134. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/office/test.xltm +0 -0
  135. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/office/test.xltx +0 -0
  136. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/office/test_utf16le.csv +0 -0
  137. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/office/text_cr.txt +0 -0
  138. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/office/text_crlf.txt +0 -0
  139. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/office/text_lf.txt +0 -0
  140. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/system/test.exe +0 -0
  141. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/system/test.json +0 -0
  142. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/system/test.puremagic_multi_footer +0 -0
  143. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/system/test.py +0 -0
  144. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/system/test_list.json +0 -0
  145. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/video/test.3g2 +0 -0
  146. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/video/test.avi +0 -0
  147. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/video/test.flv +0 -0
  148. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/video/test.jxsv +0 -0
  149. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/video/test.mkv +0 -0
  150. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/video/test.mp4 +0 -0
  151. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/video/test.mpg +0 -0
  152. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/video/test.ogv +0 -0
  153. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/video/test.webm +0 -0
  154. {puremagic-2.2.0 → puremagic-2.2.1}/test/resources/video/test.wmv +0 -0
@@ -0,0 +1,27 @@
1
+ # Releases only: every change lands on develop first, then ships to master
2
+ # as a single versioned release PR from develop.
3
+
4
+ name: Master Source Guard
5
+
6
+ on:
7
+ pull_request:
8
+ branches: [ master ]
9
+ types: [ opened, reopened, synchronize, edited ]
10
+
11
+ permissions: {}
12
+
13
+ jobs:
14
+ require-develop:
15
+ runs-on: ubuntu-latest
16
+ timeout-minutes: 5
17
+ steps:
18
+ - name: Require PRs into master to come from develop
19
+ env:
20
+ HEAD_REF: ${{ github.head_ref }}
21
+ HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
22
+ BASE_REPO: ${{ github.repository }}
23
+ run: |
24
+ if [ "$HEAD_REF" != "develop" ] || [ "$HEAD_REPO" != "$BASE_REPO" ]; then
25
+ echo "::error::PRs into master must come from $BASE_REPO:develop (got $HEAD_REPO:$HEAD_REF). Please retarget this PR to develop."
26
+ exit 1
27
+ fi
@@ -0,0 +1,69 @@
1
+ # Builds and publishes to PyPI when a GitHub release is created.
2
+ # Uses PyPI Trusted Publishing (OIDC), so no PyPI password or token is stored in GitHub:
3
+ # https://docs.pypi.org/trusted-publishers/
4
+ # All actions are pinned to full commit SHAs; the comment shows the matching release tag.
5
+
6
+ name: Upload Python Package
7
+
8
+ on:
9
+ release:
10
+ types: [created]
11
+
12
+ permissions: {}
13
+
14
+ jobs:
15
+ build:
16
+ name: Build distribution
17
+ runs-on: ubuntu-latest
18
+ timeout-minutes: 15
19
+ permissions:
20
+ contents: read
21
+
22
+ steps:
23
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
24
+ with:
25
+ persist-credentials: false
26
+
27
+ - name: Set up Python
28
+ uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
29
+ with:
30
+ python-version: '3.15'
31
+
32
+ - name: Install uv
33
+ uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
34
+
35
+ - name: Install dependencies
36
+ run: uv sync --frozen
37
+
38
+ - name: Build and check distribution
39
+ run: |
40
+ uv build
41
+ uv run twine check dist/*
42
+
43
+ - name: Store distribution
44
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
45
+ with:
46
+ name: python-package-distributions
47
+ path: dist/
48
+ if-no-files-found: error
49
+
50
+ publish:
51
+ name: Publish to PyPI
52
+ needs: build
53
+ runs-on: ubuntu-latest
54
+ timeout-minutes: 15
55
+ environment:
56
+ name: pypi
57
+ url: https://pypi.org/p/puremagic
58
+ permissions:
59
+ id-token: write # Required for Trusted Publishing; the only permission this job has
60
+
61
+ steps:
62
+ - name: Download distribution
63
+ uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
64
+ with:
65
+ name: python-package-distributions
66
+ path: dist/
67
+
68
+ - name: Publish to PyPI
69
+ uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # v1.14.3
@@ -16,24 +16,25 @@ jobs:
16
16
  fail-fast: false
17
17
  matrix:
18
18
  os: [ubuntu-latest]
19
- python-version: ["3.12", "3.13", "3.14"]
19
+ python-version: ["3.12", "3.13", "3.14", "3.15"]
20
20
  include:
21
21
  - os: macos-latest
22
- python-version: '3.14'
22
+ python-version: '3.15'
23
23
  - os: windows-latest
24
- python-version: '3.14'
24
+ python-version: '3.15'
25
25
  runs-on: ${{ matrix.os }}
26
+ timeout-minutes: 15
26
27
  steps:
27
- - uses: actions/checkout@v4
28
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
28
29
 
29
30
  - name: Set up Python ${{ matrix.python-version }}
30
- uses: actions/setup-python@v5
31
+ uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
31
32
  with:
32
33
  python-version: ${{ matrix.python-version }}
33
34
  allow-prereleases: true
34
35
 
35
36
  - name: Install the latest version of uv and activate the environment
36
- uses: astral-sh/setup-uv@v6
37
+ uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
37
38
 
38
39
  - name: Install dependencies
39
40
  run: uv sync --frozen
@@ -35,7 +35,7 @@ repos:
35
35
 
36
36
  - repo: https://github.com/astral-sh/ruff-pre-commit
37
37
  # Ruff version.
38
- rev: v0.15.5
38
+ rev: v0.17.3
39
39
  hooks:
40
40
  # Run the linter.
41
41
  - id: ruff-check
@@ -53,7 +53,7 @@ repos:
53
53
  pass_filenames: false
54
54
 
55
55
  - repo: https://github.com/codespell-project/codespell
56
- rev: v2.4.1
56
+ rev: v2.4.3
57
57
  hooks:
58
58
  - id: codespell
59
59
  args: [--ignore-words-list, "addin,caf,tye,wors"]
@@ -64,11 +64,11 @@ repos:
64
64
  )
65
65
 
66
66
  - repo: https://github.com/tox-dev/pyproject-fmt
67
- rev: v2.16.2
67
+ rev: v2.30.3
68
68
  hooks:
69
69
  - id: pyproject-fmt
70
70
 
71
71
  - repo: https://github.com/abravalheri/validate-pyproject
72
- rev: v0.25
72
+ rev: v0.27
73
73
  hooks:
74
74
  - id: validate-pyproject
@@ -1,6 +1,19 @@
1
1
  Changelog
2
2
  =========
3
3
 
4
+ Version 2.2.1
5
+ -------------
6
+
7
+ - Fixing #150 shell scripts with `#!/bin/sh`, `bash`, or `zsh` shebangs are now identified as `.sh` / `application/x-shellscript` instead of plain text (thanks to jbleyel and Sai Asish Y)
8
+ - Fixing #152 CSV detection only sniffs a bounded sample, so large files (such as HTML exported from Excel as `.xls`) no longer take minutes to identify, and HTML starting with an uppercase `<HTML` tag is detected (thanks to nikilase)
9
+ - Fixing #156 ISO media files (HEIC, AVIF, MP4, 3GP, MOV) are identified by their `ftyp` major brand instead of the box size (thanks to Nick Gigliotti and zhouh)
10
+ - Fixing #157 CSV files with quoted delimiters, escaped quotes, or multiline fields are no longer rejected (thanks to zhouh)
11
+ - Fixing #164 JPEG data without a filename hint now reports `.jpg` instead of `.jfif` (thanks to Gil Forcada Codinachs)
12
+ - Fixing OpenDocument templates (`.ott`, `.ots`, `.otp`) are no longer reported as regular documents during ZIP scanning (thanks to pabszero)
13
+ - Fixing file extensions are taken from the file's base name, so dots in directory names no longer produce bogus extensions (thanks to rui-zju)
14
+ - Adding Python 3.15 to tested and supported versions
15
+ - Changing PyPI releases to use Trusted Publishing, with GitHub Actions pinned to commit SHAs and upgraded off the deprecated Node.js 20 runtime
16
+
4
17
  Version 2.2.0
5
18
  -------------
6
19
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: puremagic
3
- Version: 2.2.0
3
+ Version: 2.2.1
4
4
  Summary: Pure python implementation of magic file detection
5
5
  Author-email: Chris Griffith <chris@cdgriffith.com>
6
6
  License-Expression: MIT
@@ -14,6 +14,7 @@ Classifier: Programming Language :: Python :: 3 :: Only
14
14
  Classifier: Programming Language :: Python :: 3.12
15
15
  Classifier: Programming Language :: Python :: 3.13
16
16
  Classifier: Programming Language :: Python :: 3.14
17
+ Classifier: Programming Language :: Python :: 3.15
17
18
  Classifier: Programming Language :: Python :: Implementation :: PyPy
18
19
  Classifier: Topic :: Utilities
19
20
  Requires-Python: >=3.12
@@ -1,3 +1,2 @@
1
- #!/usr/bin/env python
2
- from puremagic.main import * # noqa: F403
1
+ from puremagic.main import *
3
2
  from puremagic.main import __author__, __version__ # noqa: F401
@@ -733,6 +733,7 @@
733
733
  ["437265617469766520566f6963652046696c651a", 0, ".voc", "audio/x-voc", "Creative Voice File"],
734
734
  ["595556344d504547",0, ".y4m", "video/x-yuv4mpeg", "YUV4MPEG2 video file"],
735
735
  ["3c68746d6c", 0, ".html", "text/html", "HTML File"],
736
+ ["3c48544d4c", 0, ".html", "text/html", "HTML File"],
736
737
  ["424c5545", 0, ".bvr", "", "Blue Iris Video File"],
737
738
  ["2d2d2d2d2d424547494e20504b4353372d2d2d2d2d", 0, ".p7b", "", "PKCS 7 Certificate File" ],
738
739
  ["7b22", 0, ".json", "application/json", "JSON File"],
@@ -856,6 +857,7 @@
856
857
  ],
857
858
  ["7a626578", 0, ".info", "", "ZoomBrowser Image Index"],
858
859
  ["425a68", 0, ".tbz2", "application/x-bzip2", "bzip2 compressed archive"],
860
+ ["ffd8ff", 0, ".jpg", "image/jpeg", "JPEG|EXIF|SPIFF images"],
859
861
  ["ffd8ff", 0, ".jfif", "image/jpeg", "JPEG|EXIF|SPIFF images"],
860
862
  ["514649", 0, ".qemu", "", "Qcow Disk Image"],
861
863
  ["504b5c3030335c303034", 0, ".epub", "application/epub+zip", "electronic book document"],
@@ -903,6 +905,19 @@
903
905
  ["23212f7573722f6c6f63616c2f62696e2f707974686f6e", 0, ".wsgi", "text/x-python", "Python script"],
904
906
  ["2321202f7573722f6c6f63616c2f62696e2f707974686f6e", 0, ".wsgi", "text/x-python", "Python script"],
905
907
  ["6576616c205c2265786563202f7573722f6c6f63616c2f62696e2f707974686f6e", 0, ".wsgi", "text/x-python", "Python script"],
908
+ ["23212f62696e2f7368", 0, ".sh", "application/x-sh", "POSIX shell script"],
909
+ ["2321202f62696e2f7368", 0, ".sh", "application/x-sh", "POSIX shell script"],
910
+ ["23212f7573722f62696e2f7368", 0, ".sh", "application/x-sh", "POSIX shell script"],
911
+ ["23212f7573722f62696e2f656e762073680a", 0, ".sh", "application/x-sh", "POSIX shell script"],
912
+ ["23212f62696e2f64617368", 0, ".sh", "application/x-sh", "Dash shell script"],
913
+ ["23212f62696e2f62617368", 0, ".sh", "application/x-shellscript", "Bash shell script"],
914
+ ["2321202f62696e2f62617368", 0, ".sh", "application/x-shellscript", "Bash shell script"],
915
+ ["23212f7573722f62696e2f62617368", 0, ".sh", "application/x-shellscript", "Bash shell script"],
916
+ ["23212f7573722f62696e2f656e762062617368", 0, ".sh", "application/x-shellscript", "Bash shell script"],
917
+ ["23212f7573722f6c6f63616c2f62696e2f62617368", 0, ".sh", "application/x-shellscript", "Bash shell script"],
918
+ ["23212f62696e2f7a7368", 0, ".sh", "application/x-shellscript", "Zsh shell script"],
919
+ ["23212f7573722f62696e2f7a7368", 0, ".sh", "application/x-shellscript", "Zsh shell script"],
920
+ ["23212f7573722f62696e2f656e76207a73680a", 0, ".sh", "application/x-shellscript", "Zsh shell script"],
906
921
  ["53756d6d6172793a20", 0, ".spec", "text/x-rpm-spec", "RPM spec file"],
907
922
  ["25646566696e6520", 0, ".spec", "text/x-rpm-spec", "RPM spec file"],
908
923
  ["667479707174", 4, ".qtvr", "video/quicktime", "QuickTime video"],
@@ -1144,7 +1159,6 @@
1144
1159
  ["b5a2b0b3b3b0a5b5", 0, ".cal", "", "Windows calendar"],
1145
1160
  ["537570657243616c", 0, ".cal", "", "SuperCalc worksheet"],
1146
1161
  ["504e4349554e444f", 0, ".dat", "", "Norton Disk Doctor undo file"],
1147
- ["0000001466747970", 0, ".3gp", "", "3GPP multimedia files"],
1148
1162
  ["5b57696e646f7773", 0, ".cpx", "", "Microsoft Code Page Translation file"],
1149
1163
  ["a90d000000000000", 0, ".dat", "", "Access Data FTK evidence"],
1150
1164
  ["424f4f4b4d4f4249", 0, ".prc", "", "Palmpilot resource file"],
@@ -1380,7 +1394,6 @@
1380
1394
  ["425a68", 0, ".bz2", "application/x-bzip2", "bzip2 compressed archive"],
1381
1395
  ["425a68", 0, ".tb2", "application/x-bzip2", "bzip2 compressed archive"],
1382
1396
  ["ffd8ff", 0, ".jpe", "image/jpeg", "JPEG|EXIF|SPIFF images"],
1383
- ["ffd8ff", 0, ".jpg", "image/jpeg", "JPEG|EXIF|SPIFF images"],
1384
1397
  ["2d6c68", 2, ".lha", "application/x-lha", "Compressed archive"],
1385
1398
  ["445644", 0, ".dvr", "", "DVR-Studio stream file"],
1386
1399
  ["1a0000", 0, ".ntf", "", "Lotus Notes database template"],
@@ -1655,6 +1668,7 @@
1655
1668
  ["fff0", 0, ".aac", "audio/aac", "AAC audio"],
1656
1669
  ["667479704d3441", 4, ".f4a", "audio/mp4", "MPEG-4 audio"],
1657
1670
  ["6674797069736f6d", 4, ".mp4", "video/mp4", "MPEG-4 video"],
1671
+ ["667479706d703431", 4, ".mp4", "video/mp4", "MPEG-4 video"],
1658
1672
  ["667479706d703432", 4, ".mp4", "video/mp4", "MPEG-4 video"],
1659
1673
  ["667479704d534e56", 4, ".mp4", "video/mp4", "MPEG-4 video"],
1660
1674
  ["667479704d345620", 4, ".mp4", "video/mp4", "MPEG-4 video"],
@@ -2040,7 +2054,6 @@
2040
2054
  ["fff9", 0, ".aac", "audio/aac", "MPEG-2 AAC audio"],
2041
2055
  ["fff1", 0, ".aac", "audio/aac", "MPEG-4 AAC audio"],
2042
2056
  ["6674797033677035", 4, ".mp4", "video/mp4", "MPEG-4 video file"],
2043
- ["0000001c66747970", 0, ".mp4", "video/mp4", "MPEG-4 video"],
2044
2057
  ["fdffffff02", 512, ".pub", "", "MS Publisher file subheader"],
2045
2058
  ["53505642", 0, ".spvb", "", "MultiBit Bitcoin blockchain file"],
2046
2059
  ["0a166f72672e626974636f696e2e7072", 0, ".wallet", "", "MultiBit Bitcoin wallet file"],
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env python
2
1
  """
3
2
  puremagic is a pure python module that will identify a file based off it's
4
3
  magic numbers. It is designed to be minimalistic and inherently cross-platform
@@ -23,38 +22,39 @@ import puremagic
23
22
 
24
23
  if os.getenv("PUREMAGIC_DEEPSCAN") != "0":
25
24
  from puremagic.scanners import (
26
- zip_scanner,
27
- pdf_scanner,
28
- text_scanner,
25
+ asf_scanner,
26
+ cfbf_scanner,
27
+ ebml_scanner,
28
+ hdf5_scanner,
29
29
  json_scanner,
30
- python_scanner,
31
- sndhdr_scanner,
32
30
  mpeg_audio_scanner,
33
- hdf5_scanner,
34
- cfbf_scanner,
35
31
  ogg_scanner,
36
- asf_scanner,
37
- ebml_scanner,
32
+ pdf_scanner,
33
+ python_scanner,
34
+ sndhdr_scanner,
35
+ text_scanner,
36
+ zip_scanner,
38
37
  )
39
38
 
40
39
  __author__ = "Chris Griffith"
41
- __version__ = "2.2.0"
40
+ __version__ = "2.2.1"
42
41
  __all__ = [
43
- "magic_file",
44
- "magic_string",
45
- "magic_stream",
46
- "magic_extension",
47
- "from_file",
48
- "from_string",
49
- "from_stream",
50
- "from_extension",
51
- "ext_from_filename",
52
42
  "PureError",
53
43
  "PureMagic",
54
44
  "PureMagicWithConfidence",
45
+ "ext_from_filename",
46
+ "from_extension",
47
+ "from_file",
48
+ "from_stream",
49
+ "from_string",
50
+ "magic_extension",
51
+ "magic_file",
52
+ "magic_stream",
53
+ "magic_string",
55
54
  ]
56
55
 
57
56
  here = os.path.abspath(os.path.dirname(__file__))
57
+ DEFAULT_MAGIC_DATA = os.path.join(here, "magic_data.json")
58
58
 
59
59
  PureMagic = namedtuple(
60
60
  "PureMagic",
@@ -88,7 +88,7 @@ class PureValueError(ValueError):
88
88
 
89
89
 
90
90
  def magic_data(
91
- filename: os.PathLike | str = os.path.join(here, "magic_data.json"),
91
+ filename: os.PathLike | str = DEFAULT_MAGIC_DATA,
92
92
  ) -> tuple[list[PureMagic], list[PureMagic], list[PureMagic], dict[bytes, list[PureMagic]]]:
93
93
  """Read the magic file"""
94
94
  with open(filename, encoding="utf-8") as f:
@@ -270,7 +270,7 @@ def ext_from_filename(filename: os.PathLike | str) -> str:
270
270
  :return: the extension off the end (empty string if it can't find one)
271
271
  """
272
272
  try:
273
- base, ext = str(filename).lower().rsplit(".", 1)
273
+ base, ext = os.path.basename(filename).lower().rsplit(".", 1)
274
274
  except ValueError:
275
275
  return ""
276
276
  ext = f".{ext}"
@@ -529,22 +529,18 @@ def run_deep_scan(
529
529
  name=result.name,
530
530
  )
531
531
  ]
532
- try:
533
- result = catch_all_deep_scan(filename, head, foot)
534
- except Exception:
535
- raise
536
- else:
537
- if result:
538
- return [
539
- PureMagicWithConfidence(
540
- confidence=result.confidence,
541
- byte_match=None,
542
- offset=None,
543
- extension=result.extension,
544
- mime_type=result.mime_type,
545
- name=result.name,
546
- )
547
- ]
532
+ result = catch_all_deep_scan(filename, head, foot)
533
+ if result:
534
+ return [
535
+ PureMagicWithConfidence(
536
+ confidence=result.confidence,
537
+ byte_match=None,
538
+ offset=None,
539
+ extension=result.extension,
540
+ mime_type=result.mime_type,
541
+ name=result.name,
542
+ )
543
+ ]
548
544
  if raise_on_none:
549
545
  raise PureError("Could not identify file")
550
546
 
@@ -594,8 +590,8 @@ def run_deep_scan(
594
590
 
595
591
 
596
592
  def command_line_entry(*args):
597
- import sys # noqa: PLC0415
598
- from argparse import ArgumentParser # noqa: PLC0415
593
+ import sys
594
+ from argparse import ArgumentParser
599
595
 
600
596
  parser = ArgumentParser(
601
597
  description=(
@@ -115,9 +115,8 @@ def main(file_path: os.PathLike, head: bytes, foot: bytes) -> Match | None:
115
115
  return None
116
116
 
117
117
  # Verify magic bytes
118
- if head[:8] != match_bytes:
119
- if head[:4] != match_bytes_short:
120
- return None
118
+ if head[:8] != match_bytes and head[:4] != match_bytes_short:
119
+ return None
121
120
 
122
121
  # Parse CFBF header
123
122
  sector_shift = struct.unpack_from("<H", head, 30)[0]