Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added command line argument for csv / json. csv is default #9

Merged
merged 15 commits into from
Dec 15, 2024
Merged
Show file tree
Hide file tree
Changes from 7 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
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
**/**.DS_Store
**/*.csv
**/*.json
**/.vscode
**/.txt
repopack-output.txt


# Added by cargo
joshkenney marked this conversation as resolved.
Show resolved Hide resolved

/target
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
joshkenney marked this conversation as resolved.
Show resolved Hide resolved
name = "itunes-db-parser"
version = "0.1.0"
edition = "2021"

[dependencies]
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,10 @@ The 7 possible "type" options are:


```bash
$ /target/debug/itunesdb_parser <path-to-itunesdb-file> '<type>'
$ cd iTunesDB-Parser
joshkenney marked this conversation as resolved.
Show resolved Hide resolved
$ cargo init
$ cargo build
$ ./parser/target/debug/itunesdb_parser <path-to-itunesdb-file> '<type>'
```

For iTunesDB, Photos Database, Equalizer files, and Playcounts files, a CSV will be generated with all the relevant information. For example, if you run it on an iTunesDB file, the output CSV will contain the info for all songs and podcasts mentioned in the iTunesDB file.
Expand Down Expand Up @@ -113,4 +116,4 @@ Special thanks to:

for providing iTunesDB files!

<a href="https://www.buymeacoffee.com/raleighlittles" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy Me A Coffee" height="45" width="175"></a>
<a href="https://www.buymeacoffee.com/raleighlittles" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy Me A Coffee" height="45" width="175"></a>
6 changes: 4 additions & 2 deletions parser/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
chrono = "0.4.31"
csv = "1.3.0"
chrono = { version = "0.4.31", features = ["serde"] } # Add serde feature
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

# Languages parsing
isolang = "2.4.0"
isolang = "2.4.0"
9 changes: 7 additions & 2 deletions parser/src/itunesdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@
use crate::helpers::helpers;
use crate::helpers::itunesdb_helpers;

pub struct Podcast {
use serde::Serialize;


#[derive(Serialize)]
pub struct Podcast {
pub podcast_title : String,
pub podcast_publisher : String,
pub podcast_genre : String,
Expand All @@ -35,7 +39,8 @@
}
}

pub struct Song {
#[derive(Serialize)]
pub struct Song {
pub file_extension: String,
pub bitrate_kbps: u32,
pub sample_rate_hz: u32,
Expand Down
23 changes: 22 additions & 1 deletion parser/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ mod equalizer;
use std::io::Read;

fn main() {
// add a check for the number of arguments
let args: Vec<String> = std::env::args().collect();

if args.len() < 3 {
panic!("Usage: {} <iTunes DB filename> <type> [format=csv|json]", args[0]);
}

let itunesdb_filename: String = std::env::args()
.nth(1)
.expect("Missing first parameter: iTunes DB filename");
Expand All @@ -61,6 +68,20 @@ fn main() {
);
}

// Default to "csv" if no format specified
let output_format = if args.len() > 3 {
match args[3].to_lowercase().as_str() {
"json" => "json",
"csv" => "csv",
_ => {
eprintln!("Invalid format specified. Using default 'csv'");
"csv"
}
}
} else {
"csv"
};

let mut itunesdb_file_as_bytes = Vec::new();

// https://stackoverflow.com/questions/47660946/why-does-a-file-need-to-be-mutable-to-call-readread-to-string
Expand All @@ -85,7 +106,7 @@ fn main() {
photos_csv_writer,
);
} else if itunesdb_file_type == "itunes" {
parsers::itunesdb_parser::parse_itunesdb_file(itunesdb_file_as_bytes);
parsers::itunesdb_parser::parse_itunesdb_file(itunesdb_file_as_bytes, output_format.to_string());
} else if itunesdb_file_type == "itprefs" {
parsers::preferences_parser::parse_itunes_prefs_file(itunesdb_file_as_bytes);
} else if itunesdb_file_type == "playcounts" {
Expand Down
182 changes: 107 additions & 75 deletions parser/src/parsers/itunesdb_parser.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use std::fmt::Write;
use std::fs::File;
use std::io;

use crate::constants::itunesdb_constants;
use crate::itunesdb;
Expand All @@ -7,10 +9,7 @@ use crate::helpers::helpers;
use crate::helpers::itunesdb_helpers;


pub fn parse_itunesdb_file(itunesdb_file_as_bytes : Vec<u8>) {

let mut music_csv_writer = helpers::init_csv_writer("music.csv");
let mut podcast_csv_writer = helpers::init_csv_writer("podcasts.csv");
pub fn parse_itunesdb_file(itunesdb_file_as_bytes: Vec<u8>, output_format: String) {

let mut songs_found: Vec<itunesdb::Song> = Vec::new();
let mut podcasts_found: Vec<itunesdb::Podcast> = Vec::new();
Expand Down Expand Up @@ -840,78 +839,111 @@ pub fn parse_itunesdb_file(itunesdb_file_as_bytes : Vec<u8>) {

println!("{} songs found", songs_found.len());

// Add JSON output @joshkenney
if output_format == "json" {
// Only create JSON output
if !songs_found.is_empty() {
let songs_json = serde_json::to_string_pretty(&songs_found)
.expect("Error serializing songs to JSON");
let mut songs_json_file = File::create("music.json")
.expect("Error creating songs JSON file");
io::Write::write_all(&mut songs_json_file, songs_json.as_bytes())
.expect("Error writing songs JSON file");
println!("Created music.json with {} songs", songs_found.len());
}

podcast_csv_writer.write_record(&[
"Episode Title",
"Publisher",
"Genre",
"Subtitle",
"Description",
"File Type"
]).expect("Error can't create CSV file headers for podcast file");

for episode in podcasts_found.iter() {
podcast_csv_writer.write_record(&[
episode.podcast_title.to_string(),
episode.podcast_publisher.to_string(),
episode.podcast_genre.to_string(),
episode.podcast_subtitle.to_string(),
episode.podcast_description.to_string().replace("\n", ""),
episode.podcast_file_type.to_string()
]).expect("Can't write row to podcast CSV file");
}
if !podcasts_found.is_empty() {
let podcasts_json = serde_json::to_string_pretty(&podcasts_found)
.expect("Error serializing podcasts to JSON");
let mut podcasts_json_file = File::create("podcasts.json")
.expect("Error creating podcasts JSON file");
io::Write::write_all(&mut podcasts_json_file, podcasts_json.as_bytes())
.expect("Error writing podcasts JSON file");
println!("Created podcasts.json with {} podcasts", podcasts_found.len());
}
// default to CSV output
} else {
let mut music_csv_writer = helpers::init_csv_writer("music.csv");
let mut podcast_csv_writer = helpers::init_csv_writer("podcasts.csv");

if !podcasts_found.is_empty() {
podcast_csv_writer.write_record(&[
"Episode Title",
"Publisher",
"Genre",
"Subtitle",
"Description",
"File Type"
]).expect("Error can't create CSV file headers for podcast file");

for episode in podcasts_found.iter() {
podcast_csv_writer.write_record(&[
episode.podcast_title.to_string(),
episode.podcast_publisher.to_string(),
episode.podcast_genre.to_string(),
episode.podcast_subtitle.to_string(),
episode.podcast_description.to_string().replace("\n", ""),
episode.podcast_file_type.to_string()
]).expect("Can't write row to podcast CSV file");
}
println!("Created podcasts.csv with {} podcasts", podcasts_found.len());
}

music_csv_writer
.write_record(&[
"Song Title",
"Artist",
"Album",
"Year released",
"File size",
"Song Duration",
"Filename",
"Genre",
"File extension",
"Bitrate (kbps)",
"Sample Rate (Hz)",
"File size (bytes)",
"Song duration (seconds)",
"Play count",
"Rating",
"Added to library on (timestamp)",
"Added to library on (epoch)",
"Composer",
"Comment",
])
.expect("Can't create CSV file headers for music file");

for song in songs_found.iter() {
// the duplicate `to_string()` calls are to avoid this error:
// cannot move out of `song.song_title` which is behind a shared reference
// move occurs because `song.song_title` has type `String`, which does not implement the `Copy` trait

music_csv_writer
.write_record(&[
song.song_title.to_string(),
song.song_artist.to_string(),
song.song_album.to_string(),
song.song_year.to_string(),
song.file_size_friendly.to_string(),
song.song_duration_friendly.to_string(),
song.song_filename.to_string(),
song.song_genre.to_string(),
song.file_extension.to_string(),
song.bitrate_kbps.to_string(),
song.sample_rate_hz.to_string(),
song.file_size_bytes.to_string(),
song.song_duration_s.to_string(),
song.num_plays.to_string(),
itunesdb_helpers::decode_itunes_stars(song.song_rating_raw),
song.song_added_to_library_ts.to_string(),
song.song_added_to_library_epoch.to_string(),
song.song_composer.to_string(),
song.song_comment.to_string(),
])
.expect("Can't write row to CSV");
if !songs_found.is_empty() {
music_csv_writer
.write_record(&[
"Song Title",
"Artist",
"Album",
"Year released",
"File size",
"Song Duration",
"Filename",
"Genre",
"File extension",
"Bitrate (kbps)",
"Sample Rate (Hz)",
"File size (bytes)",
"Song duration (seconds)",
"Play count",
"Rating",
"Added to library on (timestamp)",
"Added to library on (epoch)",
"Composer",
"Comment",
])
.expect("Can't create CSV file headers for music file");

for song in songs_found.iter() {
// the duplicate `to_string()` calls are to avoid this error:
// cannot move out of `song.song_title` which is behind a shared reference
// move occurs because `song.song_title` has type `String`, which does not implement the `Copy` trait

music_csv_writer
.write_record(&[
song.song_title.to_string(),
song.song_artist.to_string(),
song.song_album.to_string(),
song.song_year.to_string(),
song.file_size_friendly.to_string(),
song.song_duration_friendly.to_string(),
song.song_filename.to_string(),
song.song_genre.to_string(),
song.file_extension.to_string(),
song.bitrate_kbps.to_string(),
song.sample_rate_hz.to_string(),
song.file_size_bytes.to_string(),
song.song_duration_s.to_string(),
song.num_plays.to_string(),
itunesdb_helpers::decode_itunes_stars(song.song_rating_raw),
song.song_added_to_library_ts.to_string(),
song.song_added_to_library_epoch.to_string(),
song.song_composer.to_string(),
song.song_comment.to_string(),
])
.expect("Can't write row to CSV");
}
println!("Created music.csv with {} songs", songs_found.len());
}
}
}
3 changes: 3 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() {
joshkenney marked this conversation as resolved.
Show resolved Hide resolved
println!("Hello, world!");
}