class RBOX_SONG
Object representing Rhythmbox 3.0.1 song entry in rhythmdb.xml
See field enumeration class RBOX_DATABASE_FIELD_ENUM
note
description: "[
Object representing Rhythmbox 3.0.1 song entry in rhythmdb.xml
See field enumeration class ${RBOX_DATABASE_FIELD_ENUM}
]"
author: "Finnian Reilly"
copyright: "Copyright (c) 2001-2022 Finnian Reilly"
contact: "finnian at eiffel hyphen loop dot com"
license: "MIT license (See: en.wikipedia.org/wiki/MIT_License)"
date: "2024-10-12 7:36:58 GMT (Saturday 12th October 2024)"
revision: "61"
class
RBOX_SONG
inherit
RBOX_IGNORED_ENTRY
rename
file_path as mp3_path,
set_location as set_mp3_uri
redefine
make, getter_function_table, on_context_exit,
new_representations, Type
end
MEDIA_ITEM
rename
id as mb_trackid, -- XML field name
Default_id as Default_audio_id,
relative_path as mp3_relative_path,
checksum as last_checksum,
set_id_from_uuid as set_audio_id_from_uuid
end
RBOX_SONG_FIELDS undefine is_equal end
M3U_PLAY_LIST_CONSTANTS
EL_ZSTRING_CONSTANTS
EL_MODULE_FILE; EL_MODULE_OS; EL_MODULE_STRING
create
make
feature {NONE} -- Initialization
make
local
time: EL_TIME_ROUTINES
do
Precursor
album_artists := Default_album_artists
media_type := Media_types.mpeg
set_last_seen_time (time.Unix_origin)
set_audio_id (Default_audio_id)
set_first_seen_time (time.Unix_origin)
end
feature -- Artist
album_artists: like Default_album_artists note option: transient attribute end
artists_list: EL_ZSTRING_LIST
-- All artists including album
do
create Result.make (1 + album_artists.list.count)
Result.extend (artist)
Result.append (album_artists.list)
end
lead_artist: ZSTRING
do
Result := artist
end
title_and_album: ZSTRING
do
Result := "%S (%S)"
Result.substitute_tuple ([title, album])
end
feature -- Tags
recording_date: INTEGER
do
Result := date
end
recording_year: INTEGER
--
do
Result := recording_date // Days_in_year
end
feature -- Access
audio_id: STRING
do
Result := mb_trackid
end
file_size_mb: DOUBLE
do
Result := File.megabyte_count (mp3_path)
end
mp3_info: TL_MUSICBRAINZ_MPEG_FILE
do
create Result.make (mp3_path)
end
mp3_uri: EL_URI
do
Result := location
end
m3u_entry (tanda_index: INTEGER; is_windows_format, is_nokia_phone: BOOLEAN): ZSTRING
-- For example:
-- #EXTINF: 182, Te Aconsejo Que me Olvides -- Aníbal Troilo (Singers: Francisco Fiorentino)
-- /storage/sdcard1/Music/Tango/Aníbal Troilo/Te Aconsejo Que me Olvides.02.mp3
-- For Nokia phone:
-- E:\Music\Tango\Aníbal Troilo\Te Aconsejo Que me Olvides.02.mp3
local
artists, info: ZSTRING; tanda_name: EL_ZSTRING_LIST
destination_dir: DIR_PATH; destination_path: FILE_PATH
do
artists := lead_artist.twin
if not album_artists.list.is_empty then
artists.append (Bracket_template #$ [album_artist])
end
if is_cortina then
create tanda_name.make_word_split (title)
tanda_name.start
if not tanda_name.after and then tanda_name.item.has ('.') then
tanda_name.remove
end
tanda_name.finish
if not tanda_name.off and then tanda_name.item.has ('_') then
tanda_name.remove
end
info := M3U.info_template #$ [duration, tanda_name.joined_words, Tanda_digits.formatted (tanda_index)]
else
info := M3U.info_template #$ [duration, title, artists]
end
destination_dir := M3U.play_list_root + Music_directory
destination_path := destination_dir + exported_relative_path (is_windows_format)
if is_nokia_phone then
Result := destination_path.as_windows
else
Result := M3U.extinf + new_line.joined (info, destination_path)
end
end
relative_mp3_path: FILE_PATH
do
Result := mp3_path.relative_path (music_dir)
end
short_silence: RBOX_SONG
-- short silence played at end of song to compensate for recorded silence
local
index: INTEGER
do
if has_silence_specified then
index := beats_per_minute
else
index := 1
end
Result := Database.silence_intervals [index]
end
unique_normalized_mp3_path: FILE_PATH
--
require
not_hidden: not is_hidden
do
Result := normalized_mp3_base_path
Result.add_extension ("01.mp3")
Result := Result.next_version_path
end
feature -- Attributes
last_checksum: NATURAL
feature -- Locations
last_copied_mp3_path: FILE_PATH
mp3_relative_path: FILE_PATH
do
Result := mp3_path.relative_path (music_dir)
end
feature -- Status query
has_audio_id: BOOLEAN
do
Result := audio_id /= Default_audio_id
end
has_other_artists: BOOLEAN
--
do
Result := not album_artists.list.is_empty
end
has_silence_specified: BOOLEAN
-- true if mp3 track does not have enough silence at end and has some extra silence
-- specified by beats_per_minute
do
Result := Database.silence_intervals.valid_index (beats_per_minute)
end
is_cortina: BOOLEAN
-- Is genre a short clip used to separate a dance set (usually used in Tango dances)
do
Result := genre ~ Extra_genre.cortina
end
is_genre_silence: BOOLEAN
-- Is genre a short silent clip used to pad a song
do
Result := genre ~ Extra_genre.silence
end
is_modified: BOOLEAN
--
do
Result := last_checksum /= main_fields_checksum
end
is_mp3_format: BOOLEAN
do
Result := mp3_path.extension ~ Mp3_extension
end
is_mp3_path_normalized: BOOLEAN
-- Does the mp3 path conform to:
-- <mp3_root_location>/<genre>/<artist>/<title>.<version number>.mp3
require
not_hidden: not is_hidden
local
norm_path, actual_path: FILE_PATH
do
actual_path := mp3_path.relative_path (music_dir).without_extension
norm_path := normalized_path_steps
if actual_path.parent ~ norm_path.parent then
Result := actual_path.base_matches (norm_path.base, False)
end
end
feature -- Element change
move_mp3_to_normalized_file_path
-- move mp3 file to directory relative to root <genre>/<lead artist>
-- and delete empty directory at source
require
not_hidden: not is_hidden
local
old_mp3_path: like mp3_path
do
old_mp3_path := mp3_path
set_mp3_uri (unique_normalized_mp3_path)
File_system.make_directory (mp3_path.parent)
OS.move_file (old_mp3_path, mp3_path)
if old_mp3_path.parent.exists then
File_system.delete_empty_branch (old_mp3_path.parent)
end
end
set_album_artists (text: ZSTRING)
--
local
csv_list: EL_ZSTRING_LIST; l_type: ZSTRING
field: EL_COLON_FIELD_ROUTINES
do
if not (text.is_empty or text ~ Unknown_string) then
l_type := field.name (text)
if l_type.is_empty then
csv_list := text
album_artists := [Unknown_string, csv_list]
else
csv_list := field.value (text)
Artist_type_list.find_first_true (agent String.starts_with (l_type, ?))
if Artist_type_list.after then
album_artists := [l_type, csv_list]
else
album_artists := [Artist_type_list.item, csv_list]
end
end
end
end
set_audio_id (id: STRING)
do
mb_trackid := id
end
set_first_seen_time (a_first_seen_time: like first_seen_time)
local
time: EL_TIME_ROUTINES
do
first_seen := time.unix_date_time (a_first_seen_time)
end
set_music_dir (a_music_dir: like music_dir)
--
do
music_dir := a_music_dir
end
set_mp3_path (a_mp3_path: like mp3_path)
do
set_mp3_uri (a_mp3_path)
end
set_recording_date (a_recording_date: like recording_date)
--
do
date := a_recording_date
end
update_checksum
--
do
last_checksum := main_fields_checksum
end
update_audio_id
local
mp3: like mp3_info
do
if mp3_path.exists then
set_audio_id_from_uuid (new_audio_id)
mp3 := mp3_info
mp3.set_recording_id (mb_trackid)
mp3.save
mp3.dispose
update_file_info
end
end
update_file_info
do
mtime := File.modification_time (mp3_path)
file_size := File.byte_count (mp3_path)
end
feature -- Basic operations
save_id3_info
local
mp3: like mp3_info
do
mp3 := mp3_info
write_id3_info (mp3)
mp3.dispose
update_file_info
end
write_id3_info (mp3: TL_MUSICBRAINZ_MPEG_FILE)
--
do
mp3.tag.set_title (title)
mp3.tag.set_artist (artist)
mp3.tag.set_genre (genre)
mp3.tag.set_album (album)
mp3.tag.set_album_artist (album_artist)
if composer ~ Unknown_string then
mp3.tag.set_composer (Empty_string)
else
mp3.tag.set_composer (composer)
end
if track_number > 0 then
mp3.tag.set_track (track_number)
end
if beats_per_minute > 0 then
mp3.tag.set_beats_per_minute (beats_per_minute)
end
mp3.tag.set_year_from_days (recording_date)
mp3.tag.set_comment_with (ID3_comment_description, comment)
mp3.set_album_artist_id (mb_albumartistid)
mp3.set_album_id (mb_albumid)
mp3.set_artist_id (mb_artistid)
mp3.set_artist_sort_name (mb_artistsortname)
mp3.set_recording_id (mb_trackid)
mp3.save
end
feature {NONE} -- Implementation
main_fields_checksum: NATURAL
--
local
crc: like crc_generator; l_picture_checksum: NATURAL
do
crc := crc_generator
crc.add_tuple ([artists_list.comma_separated, album, title, genre, comment, recording_date])
l_picture_checksum := album_picture_checksum
if l_picture_checksum > 0 then
crc.add_natural (l_picture_checksum)
end
Result := crc.checksum
end
new_audio_id: EL_UUID
require
path_exists: mp3_path.exists
local
mp3: EL_MP3_IDENTIFIER
do
create mp3.make (mp3_path)
Result := mp3.audio_id
end
new_representations: like Default_representations
do
Result := Precursor + ["composer", Composer_set.to_representation]
end
normalized_mp3_base_path: FILE_PATH
-- normalized path <mp3_root_location>/<genre>/<artist>/<title>[<- vocalists>]
do
Result := music_dir.plus_file (normalized_path_steps)
end
normalized_path_steps: FILE_PATH
-- normalized path steps <genre>,<artist>,<title>
do
create Result.make_from_steps (
<< genre, artist, title.translated (Problem_file_name_characters, Problem_file_name_substitutes)
>>)
-- Remove problematic characters from last step of name
end
feature {NONE} -- Build from XML
on_context_exit
-- Called when the parser leaves the current context
do
update_checksum
set_album_artists (album_artist)
end
feature {NONE} -- Evolicity reflection
getter_function_table: like getter_functions
--
do
Result := Precursor
Result.append_tuples (<<
["artists", agent: ZSTRING do Result := Xml.escaped (artists_list.comma_separated) end],
["lead_artist", agent: ZSTRING do Result := Xml.escaped (lead_artist) end],
["album_artists", agent: ZSTRING do Result := Xml.escaped (album_artist) end],
["artist_list", agent: ITERABLE [ZSTRING] do Result := artists_list end],
["duration_time", agent formatted_duration_time],
["last_checksum", agent: NATURAL_32_REF do Result := last_checksum.to_reference end],
["recording_year", agent: INTEGER_REF do Result := recording_year.to_reference end],
["is_hidden", agent: BOOLEAN_REF do Result := is_hidden.to_reference end],
["is_cortina", agent: BOOLEAN_REF do Result := is_cortina.to_reference end],
["has_other_artists", agent: BOOLEAN_REF do Result := has_other_artists.to_reference end]
>>)
end
feature -- Constants
Problem_file_name_characters: ZSTRING
once
Result := "\/"
end
Problem_file_name_substitutes: ZSTRING
once
create Result.make_filled ('-', Problem_file_name_characters.count)
end
Type: STRING
once
Result := "song"
end
end