-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit ff31e04
Showing
8 changed files
with
373 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
name: Rust CD | ||
|
||
on: | ||
push: | ||
tags: | ||
- "*.*.*" | ||
|
||
jobs: | ||
publish-binary: | ||
runs-on: ${{ matrix.platform.os }} | ||
strategy: | ||
matrix: | ||
rust: [ stable ] | ||
platform: | ||
- os: macos-latest | ||
os-name: macos | ||
target: x86_64-apple-darwin | ||
architecture: x86_64 | ||
binary-postfix: "" | ||
use-cross: false | ||
- os: ubuntu-latest | ||
os-name: linux | ||
target: x86_64-unknown-linux-gnu | ||
architecture: x86_64 | ||
binary-postfix: "" | ||
use-cross: false | ||
- os: windows-latest | ||
os-name: windows | ||
target: x86_64-pc-windows-msvc | ||
architecture: x86_64 | ||
binary-postfix: ".exe" | ||
use-cross: false | ||
- os: ubuntu-latest | ||
os-name: linux | ||
target: aarch64-unknown-linux-gnu | ||
architecture: arm64 | ||
binary-postfix: "" | ||
use-cross: true | ||
- os: ubuntu-latest | ||
os-name: linux | ||
target: i686-unknown-linux-gnu | ||
architecture: i686 | ||
binary-postfix: "" | ||
use-cross: true | ||
|
||
steps: | ||
- name: Install Rust toolchain | ||
uses: actions-rs/toolchain@v1 | ||
with: | ||
toolchain: ${{ matrix.rust }} | ||
profile: minimal | ||
override: true | ||
|
||
- name: Checkout repository | ||
uses: actions/checkout@v2 | ||
|
||
- name: Cargo build | ||
uses: actions-rs/cargo@v1 | ||
with: | ||
command: build | ||
use-cross: ${{ matrix.platform.use-cross }} | ||
toolchain: ${{ matrix.rust }} | ||
args: --release --target ${{ matrix.platform.target }} | ||
|
||
- name: Install strip command | ||
if: ${{ matrix.platform.target == 'aarch64-unknown-linux-gnu' }} | ||
shell: bash | ||
run: | | ||
sudo apt update | ||
sudo apt-get install -y binutils-aarch64-linux-gnu | ||
- name: Package final binary | ||
shell: bash | ||
run: | | ||
cd target/${{ matrix.platform.target }}/release | ||
####### reduce binary size by removing debug symbols ####### | ||
BINARY_NAME=jpegscans${{ matrix.platform.binary-postfix }} | ||
if [[ ${{ matrix.platform.target }} == aarch64-unknown-linux-gnu ]]; then | ||
GCC_PREFIX="aarch64-linux-gnu-" | ||
else | ||
GCC_PREFIX="" | ||
fi | ||
if [[ ${{ matrix.platform.target }} != x86_64-pc-windows-msvc ]]; then | ||
"$GCC_PREFIX"strip $BINARY_NAME | ||
fi | ||
########## create tar.gz ########## | ||
RELEASE_NAME=jpegscans-${GITHUB_REF/refs\/tags\//}-${{ matrix.platform.os-name }}-${{ matrix.platform.architecture }} | ||
tar czvf $RELEASE_NAME.tar.gz $BINARY_NAME | ||
########## create sha256 ########## | ||
if [[ ${{ runner.os }} == 'Windows' ]]; then | ||
certutil -hashfile $RELEASE_NAME.tar.gz sha256 | grep -E [A-Fa-f0-9]{64} > $RELEASE_NAME.sha256 | ||
else | ||
shasum -a 256 $RELEASE_NAME.tar.gz > $RELEASE_NAME.sha256 | ||
fi | ||
- name: Releasing assets | ||
uses: softprops/action-gh-release@v1 | ||
with: | ||
files: | | ||
target/${{ matrix.platform.target }}/release/jpegscans-*.tar.gz | ||
target/${{ matrix.platform.target }}/release/jpegscans-*.sha256 | ||
env: | ||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
name: Rust CI | ||
|
||
on: | ||
push: | ||
branches: master | ||
pull_request: | ||
branches: master | ||
schedule: | ||
- cron: "0 0 1 * *" # monthly | ||
workflow_dispatch: # allow manual triggering of the action | ||
|
||
env: | ||
RUSTFLAGS: "-Dwarnings" | ||
|
||
jobs: | ||
build-crate: | ||
name: Build and test crate/docs | ||
runs-on: ${{ matrix.os }} | ||
strategy: | ||
matrix: | ||
os: [ubuntu-latest, windows-latest] | ||
toolchain: [nightly, beta, stable] | ||
include: | ||
- os: macos-latest | ||
toolchain: stable | ||
steps: | ||
- uses: actions/checkout@v2 | ||
- uses: actions-rs/toolchain@v1 | ||
with: | ||
profile: minimal | ||
toolchain: ${{ matrix.toolchain }} | ||
components: rust-docs | ||
override: true | ||
- name: Build library | ||
run: cargo build -v --lib --no-default-features | ||
- name: Build binary | ||
run: cargo build -v --bins | ||
|
||
clippy-rustfmt: | ||
name: Clippy and rustfmt | ||
runs-on: ubuntu-latest | ||
continue-on-error: true | ||
steps: | ||
- uses: actions/checkout@v2 | ||
- uses: actions-rs/toolchain@v1 | ||
with: | ||
profile: minimal | ||
toolchain: stable | ||
components: clippy, rustfmt | ||
override: true | ||
- name: clippy | ||
run: cargo clippy | ||
continue-on-error: true | ||
- name: rustfmt | ||
run: cargo fmt -- --check | ||
continue-on-error: true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
/target | ||
/*.jpg |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
[package] | ||
name = "jpegscans" | ||
version = "0.1.0" | ||
authors = ["okaneco <47607823+okaneco@users.noreply.github.com>"] | ||
edition = "2018" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
Copyright 2021 Collyn O'Kane | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
# jpegscans | ||
|
||
Reads a progressive JPEG image file and produces the successive scan images from | ||
that file. | ||
|
||
## Usage | ||
|
||
```bash | ||
jpegscans [input] [output filename prefix] | ||
``` | ||
|
||
The first argument is the `input` filename. The second argument is an optional | ||
prefix for the scan files produced from the `input`. The default output prefix | ||
is `scan`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
//! Crate for producing images of the progressive scan passes found in JPEG | ||
//! image files. | ||
use std::io::{Read, Seek}; | ||
|
||
/// Jpeg header magic bytes. | ||
pub const JPEG_MAGIC_BYTES: [u8; 3] = [0xFF, 0xD8, 0xFF]; | ||
/// Byte padding. | ||
pub const PADDING: u8 = 0x00; | ||
/// Temporary marker. | ||
pub const TEM: u8 = 0x01; | ||
/// End of image marker. | ||
pub const EOI: u8 = 0xD9; | ||
/// Start of scan marker. | ||
pub const SOS: u8 = 0xDA; | ||
/// Fill bytes (markers may be preceded by any number of these). | ||
pub const FILL: u8 = 0xFF; | ||
|
||
/// Consume all bytes in the current marker section. | ||
pub fn consume_marker_section<R: Read>(r: &mut R) -> Result<(), std::io::Error> { | ||
let mut buf = [0, 0]; | ||
r.read_exact(&mut buf)?; | ||
|
||
// Length of marker section includes length bytes, so we subtract 2 bytes | ||
let marker_length = u16::from_be_bytes(buf).saturating_sub(2); | ||
|
||
// Consume the section's bytes | ||
for _ in 0..marker_length { | ||
r.read_exact(&mut buf[..1])?; | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
/// Consume the bytes in an [SOS] marker section. | ||
pub fn consume_sos_section<R: Read + Seek>(r: &mut R) -> Result<(), std::io::Error> { | ||
loop { | ||
match find_next_marker(r)? { | ||
// Ignore Restart markers, temporary markers, and padding bytes | ||
PADDING | TEM | 0xD0..=0xD7 => {} | ||
// Rewind the cursor to process the next marker | ||
_ => { | ||
r.seek(std::io::SeekFrom::Current(-2))?; | ||
return Ok(()); | ||
} | ||
} | ||
} | ||
} | ||
|
||
/// Consume bytes until the next marker is found. | ||
pub fn find_next_marker<R: Read>(r: &mut R) -> Result<u8, std::io::Error> { | ||
let mut buf = [0]; | ||
|
||
// Find next fill byte | ||
while buf != [FILL] { | ||
r.read_exact(&mut buf)?; | ||
} | ||
|
||
// Consume all of the fill bytes that precede the marker byte | ||
while buf == [FILL] { | ||
r.read_exact(&mut buf)?; | ||
} | ||
|
||
Ok(buf[0]) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
use jpegscans::{ | ||
consume_marker_section, consume_sos_section, find_next_marker, EOI, FILL, JPEG_MAGIC_BYTES, SOS, | ||
}; | ||
|
||
const HELP_MESSAGE: &str = "jpegscans | ||
Produce scan images from a progressive JPEG file. | ||
USAGE: | ||
jpegscans [input] [output filename prefix]"; | ||
|
||
fn main() { | ||
if let Err(e) = try_main() { | ||
eprintln!("{}", e); | ||
std::process::exit(1); | ||
} | ||
} | ||
|
||
fn try_main() -> Result<(), ScanError> { | ||
let mut args = std::env::args().skip(1); | ||
|
||
let input = match args.next() { | ||
Some(s) => { | ||
if matches!(s.as_str(), "-h" | "--help") { | ||
println!("{}", HELP_MESSAGE); | ||
return Ok(()); | ||
} | ||
s | ||
} | ||
None => { | ||
println!("{}", HELP_MESSAGE); | ||
return Ok(()); | ||
} | ||
}; | ||
let output = args.next().map_or_else(|| "scan".into(), |s| s); | ||
|
||
let file = std::fs::read(input)?; | ||
let mut cursor = std::io::Cursor::new(&file); | ||
|
||
let mut buf = [0; 3]; | ||
|
||
// Return an error if the file header doesn't match JPEG bytes | ||
std::io::Read::read_exact(&mut cursor, &mut buf)?; | ||
if buf != JPEG_MAGIC_BYTES { | ||
return Err(ScanError::InvalidFile); | ||
} | ||
|
||
let mut scan_count = 0u16; | ||
for _ in 0..u32::MAX { | ||
match find_next_marker(&mut cursor)? { | ||
SOS => { | ||
consume_sos_section(&mut cursor)?; | ||
|
||
let scan = std::fs::File::create(format!("{}_{:05}.jpg", output, scan_count))?; | ||
let mut w = std::io::BufWriter::new(scan); | ||
|
||
let stream_position = | ||
std::convert::TryFrom::try_from(std::io::Seek::stream_position(&mut cursor)?)?; | ||
|
||
// Write all data up to the current stream position, followed by an EOI marker | ||
std::io::Write::write_all(&mut w, &file[..stream_position])?; | ||
std::io::Write::write_all(&mut w, &[FILL, EOI])?; | ||
|
||
scan_count = scan_count | ||
.checked_add(1) | ||
.ok_or(ScanError::ScanCounterOverflow)?; | ||
} | ||
EOI => break, | ||
_ => consume_marker_section(&mut cursor)?, | ||
} | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
/// Error for processing progressive scan images. | ||
#[derive(Debug)] | ||
pub enum ScanError { | ||
/// An error occurred during conversion of the stream position to a `usize`. | ||
Convert(core::num::TryFromIntError), | ||
/// The supplied file isn't a JPEG. | ||
InvalidFile, | ||
/// An error occurred while opening a file, writing to a file, or reading | ||
/// from a cursor/file. | ||
Io(std::io::Error), | ||
/// The scan number counter overflowed. | ||
ScanCounterOverflow, | ||
} | ||
|
||
impl std::fmt::Display for ScanError { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
match self { | ||
Self::Convert(err) => write!(f, "{}", err), | ||
Self::InvalidFile => write!(f, "Input file must be a JPEG"), | ||
Self::Io(err) => write!(f, "{}", err), | ||
Self::ScanCounterOverflow => write!(f, "Scan counter overflow"), | ||
} | ||
} | ||
} | ||
|
||
impl std::error::Error for ScanError { | ||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { | ||
match self { | ||
Self::Convert(e) => Some(e), | ||
Self::Io(e) => Some(e), | ||
Self::InvalidFile | Self::ScanCounterOverflow => None, | ||
} | ||
} | ||
} | ||
|
||
impl std::convert::From<std::io::Error> for ScanError { | ||
fn from(error: std::io::Error) -> Self { | ||
Self::Io(error) | ||
} | ||
} | ||
|
||
impl std::convert::From<std::num::TryFromIntError> for ScanError { | ||
fn from(error: std::num::TryFromIntError) -> Self { | ||
Self::Convert(error) | ||
} | ||
} |