Skip to content

Commit 9b85fdb

Browse files
committed
Prepare repository for GitHub publication
0 parents  commit 9b85fdb

25 files changed

+1968
-0
lines changed

.github/workflows/ci.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
8+
jobs:
9+
build-and-test:
10+
runs-on: macos-latest
11+
12+
steps:
13+
- name: Checkout
14+
uses: actions/checkout@v4
15+
16+
- name: Show Swift version
17+
run: swift --version
18+
19+
- name: Install ffmpeg
20+
run: brew install ffmpeg
21+
22+
- name: Build
23+
run: swift build
24+
25+
- name: Test
26+
run: swift test
27+
28+
- name: Build release
29+
run: swift build -c release

.gitignore

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# SwiftPM / build artifacts
2+
.build/
3+
.swiftpm/
4+
Package.resolved
5+
6+
# Xcode
7+
*.xcodeproj/xcuserdata/
8+
*.xcworkspace/xcuserdata/
9+
DerivedData/
10+
11+
# macOS
12+
.DS_Store
13+
14+
# Local benchmarking / fixtures
15+
*.MP4
16+
*.mp4
17+
18+
# Temporary files
19+
*.swp
20+
*.tmp

AGENTS.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Repository Guidelines
2+
3+
## Project Structure & Module Organization
4+
This repository is a Swift Package (`Package.swift`) with one library and one CLI target:
5+
- `Sources/VideoHash/`: core library code.
6+
- `Sources/VideoHash/Models/`: shared types (`HashResult`, `HashError`, `HashConfiguration`).
7+
- `Sources/VideoHash/OSHash/` and `Sources/VideoHash/PHash/`: hash algorithm implementations.
8+
- `Sources/VideoHash/Utilities/`: logging helpers.
9+
- `Sources/TestHash/main.swift`: executable target for local manual checks.
10+
- `Tests/VideoHashTests/`: automated tests (currently OSHash-focused).
11+
- `.build/`: local build artifacts (do not edit or commit).
12+
13+
## Build, Test, and Development Commands
14+
Run from repository root:
15+
- `swift build`: compile all targets in debug mode.
16+
- `swift test`: run the test suite.
17+
- `swift run test-hash /absolute/path/to/video.mp4`: run both hashes against a real video file.
18+
- `swift package resolve`: refresh dependency resolution when package versions change.
19+
20+
## Coding Style & Naming Conventions
21+
- Swift 6.2+ and strict concurrency are enabled; prefer `async/await`, `Sendable`, and actors for shared mutable state.
22+
- Use 4-space indentation and keep one top-level type per file.
23+
- Type names use `UpperCamelCase`; methods/properties use `lowerCamelCase`.
24+
- Organize new code by feature folder (`Models`, `OSHash`, `PHash`, `Utilities`) rather than by file type.
25+
- Keep public APIs documented with concise doc comments.
26+
27+
## Testing Guidelines
28+
- Tests use Swift Testing (`import Testing`) with `@Suite` and `@Test`.
29+
- Name tests by behavior, e.g. `testHashFormat`, `testDeterministic`.
30+
- Prefer deterministic test data and temporary files; always clean up with `defer`.
31+
- Add tests for success paths, error handling, and edge constraints (file size, invalid paths, malformed input).
32+
33+
## Commit & Pull Request Guidelines
34+
Local git metadata is not present in this package snapshot, so no repository-specific commit history could be inferred. Use this baseline:
35+
- Commit style: imperative, concise subject (<=72 chars), e.g. `Add validation for short OSHash inputs`.
36+
- Keep commits scoped to one logical change.
37+
- PRs should include: purpose, behavior changes, test evidence (`swift test` output), and sample CLI usage when relevant.

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Changelog
2+
3+
## 0.1.0
4+
- Initial public release of VideoHash.
5+
- Added `VideoHashGenerator` API for generating PHash and OSHash.
6+
- Added videohashes-compatible PHash mode (ffmpeg-based preprocessing).
7+
- Added OSHash and PHash unit tests.
8+
- Added GitHub CI workflow and contributor/security documentation.

CONTRIBUTING.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Contributing
2+
3+
## Development setup
4+
1. Install Swift 6.2+ and Xcode 17+.
5+
2. Install `ffmpeg` and ensure it is on `PATH`.
6+
3. Clone the repository and build:
7+
```bash
8+
swift build
9+
```
10+
11+
## Running tests
12+
Run the full test suite before opening a PR:
13+
```bash
14+
swift test
15+
```
16+
17+
For manual parity checks against `videohashes`:
18+
```bash
19+
./.build/debug/test-hash /path/to/video.mp4
20+
/opt/bin/videohashes-amd64-macos /path/to/video.mp4
21+
```
22+
23+
## Code style
24+
- Follow Swift API Design Guidelines.
25+
- Prefer small focused types and clear error messages.
26+
- Keep concurrency-safe code (`Sendable`, actors) where shared state exists.
27+
- Add tests for bug fixes and behavior changes.
28+
29+
## Pull requests
30+
Please include:
31+
- A short problem statement and solution summary.
32+
- Notes on API changes (if any).
33+
- Evidence of validation (`swift test` output, and parity checks when PHash logic changes).
34+
35+
## Commit messages
36+
Use imperative subject lines, for example:
37+
- `Fix frame sampling to match videohashes`
38+
- `Add ffmpeg-based preprocessing path`

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Francois
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Package.swift

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// swift-tools-version: 6.2
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
4+
import PackageDescription
5+
6+
let package = Package(
7+
name: "VideoHash",
8+
platforms: [
9+
.macOS(.v26)
10+
],
11+
products: [
12+
.library(
13+
name: "VideoHash",
14+
targets: ["VideoHash"]
15+
),
16+
.executable(
17+
name: "test-hash",
18+
targets: ["TestHash"]
19+
)
20+
],
21+
dependencies: [
22+
.package(url: "https://github.com/apple/swift-log.git", from: "1.7.0")
23+
],
24+
targets: [
25+
.target(
26+
name: "VideoHash",
27+
dependencies: [
28+
.product(name: "Logging", package: "swift-log")
29+
],
30+
swiftSettings: [
31+
.enableExperimentalFeature("StrictConcurrency")
32+
]
33+
),
34+
.executableTarget(
35+
name: "TestHash",
36+
dependencies: ["VideoHash"],
37+
path: "Sources/TestHash"
38+
),
39+
.testTarget(
40+
name: "VideoHashTests",
41+
dependencies: ["VideoHash"]
42+
)
43+
]
44+
)

README.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# VideoHash
2+
3+
VideoHash is a Swift package for generating video fingerprints on macOS:
4+
- `PHash`: perceptual hash for near-duplicate detection.
5+
- `OSHash`: OpenSubtitles hash for exact file identity.
6+
7+
The default PHash pipeline is compatibility-focused and matches `peolic/videohashes` output.
8+
9+
## Requirements
10+
11+
- macOS 26.0+
12+
- Swift 6.2+
13+
- Xcode 17+
14+
- `ffmpeg` available on `PATH` (or set `HashConfiguration.ffmpegPath`)
15+
16+
## Installation
17+
18+
### Swift Package Manager
19+
20+
```swift
21+
dependencies: [
22+
.package(url: "https://github.com/fdenis75/VideoHash.git", from: "0.1.0")
23+
]
24+
```
25+
26+
Then add the product dependency:
27+
28+
```swift
29+
.product(name: "VideoHash", package: "VideoHash")
30+
```
31+
32+
## Quick Start
33+
34+
```swift
35+
import VideoHash
36+
37+
let generator = VideoHashGenerator()
38+
let url = URL(fileURLWithPath: "/path/to/video.mp4")
39+
let result = try await generator.generateHashes(for: url)
40+
41+
print("PHash: \(result.phash)")
42+
print("OSHash: \(result.oshash)")
43+
print("Duration: \(Int(result.duration))s")
44+
```
45+
46+
## Configuration
47+
48+
```swift
49+
let config = HashConfiguration(
50+
frameCount: 25,
51+
frameWidth: 160,
52+
spriteColumns: 5,
53+
spriteRows: 5,
54+
dctSize: 32,
55+
hashSize: 8,
56+
useAccelerate: false,
57+
useFFmpegFrameExtraction: true,
58+
ffmpegPath: nil
59+
)
60+
61+
let generator = VideoHashGenerator(configuration: config)
62+
```
63+
64+
Notes:
65+
- `useFFmpegFrameExtraction: true` is the default and is recommended for parity with `videohashes`.
66+
- `useFFmpegFrameExtraction: false` uses the AVFoundation/CoreGraphics path.
67+
- `phash` is emitted as lowercase hexadecimal (not zero-padded).
68+
69+
## Development
70+
71+
```bash
72+
swift build
73+
swift test
74+
swift run test-hash /path/to/video.mp4
75+
```
76+
77+
Manual parity check against the original tool:
78+
79+
```bash
80+
./.build/debug/test-hash /path/to/video.mp4
81+
/opt/bin/videohashes-amd64-macos /path/to/video.mp4
82+
```
83+
84+
## Project Layout
85+
86+
- `Sources/VideoHash/Models`: shared models and errors
87+
- `Sources/VideoHash/OSHash`: OSHash implementation
88+
- `Sources/VideoHash/PHash`: PHash pipeline
89+
- `Sources/VideoHash/Utilities`: logging helpers
90+
- `Tests/VideoHashTests`: test suite
91+
92+
## License
93+
94+
MIT. See [LICENSE](LICENSE).
95+
96+
## Credits
97+
98+
PHash compatibility is based on [`peolic/videohashes`](https://github.com/peolic/videohashes).

SECURITY.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Security Policy
2+
3+
## Supported versions
4+
Only the latest `main` branch is currently supported for security updates.
5+
6+
## Reporting a vulnerability
7+
Please do not open public issues for security reports.
8+
9+
Send details to the maintainers directly with:
10+
- A description of the issue
11+
- Reproduction steps or proof of concept
12+
- Potential impact
13+
14+
We will acknowledge receipt and provide a remediation timeline.

Sources/TestHash/main.swift

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import Foundation
2+
import VideoHash
3+
import Logging
4+
5+
@main
6+
struct TestHash {
7+
static func main() async throws {
8+
// Enable debug logging
9+
LoggingSystem.bootstrap { label in
10+
var handler = StreamLogHandler.standardOutput(label: label)
11+
handler.logLevel = .trace
12+
return handler
13+
}
14+
15+
guard CommandLine.arguments.count > 1 else {
16+
print("Usage: test-hash <video-path>")
17+
return
18+
}
19+
20+
let videoPath = CommandLine.arguments[1]
21+
let videoURL = URL(fileURLWithPath: videoPath)
22+
23+
guard FileManager.default.fileExists(atPath: videoPath) else {
24+
print("Error: File not found: \(videoPath)")
25+
return
26+
}
27+
28+
print("Testing VideoHash package (DEBUG MODE)...")
29+
print("Video: \(videoURL.lastPathComponent)")
30+
print("")
31+
32+
let startTime = Date()
33+
34+
let generator = VideoHashGenerator()
35+
let result = try await generator.generateHashes(for: videoURL)
36+
37+
let elapsed = Date().timeIntervalSince(startTime)
38+
39+
print("")
40+
print(String(repeating: "=", count: 60))
41+
print("FINAL RESULTS:")
42+
print(String(repeating: "=", count: 60))
43+
print(" PHash: \(result.phash)")
44+
print(" OSHash: \(result.oshash)")
45+
print(" Duration: \(Int(result.duration))s")
46+
print(" Time: \(String(format: "%.2f", elapsed))s")
47+
print(String(repeating: "=", count: 60))
48+
}
49+
}

0 commit comments

Comments
 (0)